From 443fc719d77777f8019f471b4df1214136d9861e Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 00:04:48 -0500 Subject: [PATCH 01/35] feat: bootstrap agentic fabric workspace --- .github/workflows/cd.yml | 76 + .github/workflows/ci.yml | 47 + .github/workflows/codeql.yml | 26 + .github/workflows/release.yml | 29 + .gitignore | 15 + .python-version | 1 + .release-please-manifest.json | 4 + AGENTS.md | 184 + LICENSE | 21 + README.md | 16 + docs/_static/agentic-crew.css | 1 + docs/agentic-workflows.rst | 41 + docs/api/index.rst | 10 + docs/architecture.rst | 129 + docs/conf.py | 35 + docs/development.rst | 29 + docs/getting-started.rst | 41 + docs/index.rst | 32 + docs/pillars.rst | 52 + docs/vendor-fabric.rst | 27 + packages/agentic-fabric/CHANGELOG.md | 39 + packages/agentic-fabric/README.md | 153 + packages/agentic-fabric/examples/README.md | 14 + packages/agentic-fabric/examples/__init__.py | 1 + .../examples/discovery_workflow.py | 47 + .../examples/runtime_context.py | 27 + .../crews/implementation_review/agents.yaml | 9 + .../crews/implementation_review/tasks.yaml | 9 + .../packages/review/.crew/manifest.yaml | 7 + .../agentic-fabric/examples/tool_registry.py | 29 + packages/agentic-fabric/pyproject.toml | 201 + .../src/agentic_fabric/__init__.py | 85 + .../src/agentic_fabric/__main__.py | 9 + .../src/agentic_fabric/agentic_data.py | 264 + .../src/agentic_fabric/base/__init__.py | 16 + .../src/agentic_fabric/base/archetypes.yaml | 75 + .../src/agentic_fabric/capabilities.py | 123 + .../src/agentic_fabric/config/__init__.py | 10 + .../src/agentic_fabric/config/agents.yaml | 101 + .../src/agentic_fabric/config/llm.py | 222 + .../src/agentic_fabric/config/tasks.yaml | 177 + .../src/agentic_fabric/core/__init__.py | 18 + .../src/agentic_fabric/core/decomposer.py | 299 + .../src/agentic_fabric/core/discovery.py | 307 + .../src/agentic_fabric/core/loader.py | 195 + .../src/agentic_fabric/core/manager.py | 284 + .../src/agentic_fabric/core/runner.py | 70 + .../agentic-fabric/src/agentic_fabric/crew.py | 159 + .../src/agentic_fabric/crews/__init__.py | 1 + .../crews/connector_builder/__init__.py | 1 + .../connector_builder/config/agents.yaml | 16 + .../crews/connector_builder/config/tasks.yaml | 14 + .../connector_builder_crew.py | 80 + .../agentic-fabric/src/agentic_fabric/main.py | 621 ++ .../src/agentic_fabric/run_flow.py | 128 + .../src/agentic_fabric/runners/__init__.py | 32 + .../src/agentic_fabric/runners/base.py | 370 ++ .../agentic_fabric/runners/crewai_runner.py | 195 + .../runners/langgraph_runner.py | 135 + .../runners/local_cli_profiles.yaml | 169 + .../runners/local_cli_runner.py | 328 + .../src/agentic_fabric/runners/registry.py | 165 + .../runners/single_agent_runner.py | 75 + .../agentic_fabric/runners/strands_runner.py | 191 + .../src/agentic_fabric/tools/__init__.py | 75 + .../src/agentic_fabric/tools/adapters.py | 123 + .../src/agentic_fabric/tools/file_tools.py | 278 + .../src/agentic_fabric/tools/registry.py | 137 + .../agentic_fabric/tools/scraping_tools.py | 69 + .../src/agentic_fabric/tools/vendor.py | 37 + .../src/agentic_fabric/utils/__init__.py | 6 + .../src/agentic_fabric/utils/files.py | 22 + packages/agentic-fabric/tests/__init__.py | 3 + packages/agentic-fabric/tests/_crew_mocker.py | 509 ++ packages/agentic-fabric/tests/conftest.py | 259 + packages/agentic-fabric/tests/e2e/README.md | 202 + packages/agentic-fabric/tests/e2e/__init__.py | 7 + packages/agentic-fabric/tests/e2e/conftest.py | 21 + .../tests/e2e/test_crewai_e2e.py | 214 + .../tests/e2e/test_langgraph_e2e.py | 225 + .../tests/e2e/test_strands_e2e.py | 264 + .../agentic-fabric/tests/test_agentic_data.py | 85 + .../agentic-fabric/tests/test_capabilities.py | 57 + .../agentic-fabric/tests/test_cli_smoke.py | 134 + .../tests/test_connector_builder_crew.py | 45 + .../agentic-fabric/tests/test_crew_mocker.py | 73 + .../tests/test_decomposer_edge_cases.py | 200 + .../agentic-fabric/tests/test_discovery.py | 462 ++ .../tests/test_discovery_nested.py | 168 + .../agentic-fabric/tests/test_examples.py | 79 + .../agentic-fabric/tests/test_file_tools.py | 227 + packages/agentic-fabric/tests/test_loader.py | 163 + packages/agentic-fabric/tests/test_manager.py | 394 ++ packages/agentic-fabric/tests/test_runners.py | 858 +++ .../tests/test_scraping_tools.py | 34 + .../tests/test_single_agent_runners.py | 416 ++ .../tests/test_tool_adapters.py | 169 + .../tests/test_tool_registry.py | 96 + .../tests/test_yaml_config_errors.py | 236 + packages/pytest-agentic-fabric/CHANGELOG.md | 3 + packages/pytest-agentic-fabric/README.md | 16 + packages/pytest-agentic-fabric/pyproject.toml | 56 + .../src/pytest_agentic_fabric/__init__.py | 9 + .../src/pytest_agentic_fabric/plugin.py | 172 + .../src/pytest_agentic_fabric/py.typed | 1 + .../tests/test_plugin.py | 73 + pyproject.toml | 8 + release-please-config.json | 15 + tox.ini | 46 + uv.lock | 5490 +++++++++++++++++ 110 files changed, 18523 insertions(+) create mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 .release-please-manifest.json create mode 100644 AGENTS.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docs/_static/agentic-crew.css create mode 100644 docs/agentic-workflows.rst create mode 100644 docs/api/index.rst create mode 100644 docs/architecture.rst create mode 100644 docs/conf.py create mode 100644 docs/development.rst create mode 100644 docs/getting-started.rst create mode 100644 docs/index.rst create mode 100644 docs/pillars.rst create mode 100644 docs/vendor-fabric.rst create mode 100644 packages/agentic-fabric/CHANGELOG.md create mode 100644 packages/agentic-fabric/README.md create mode 100644 packages/agentic-fabric/examples/README.md create mode 100644 packages/agentic-fabric/examples/__init__.py create mode 100644 packages/agentic-fabric/examples/discovery_workflow.py create mode 100644 packages/agentic-fabric/examples/runtime_context.py create mode 100644 packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/crews/implementation_review/agents.yaml create mode 100644 packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/crews/implementation_review/tasks.yaml create mode 100644 packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/manifest.yaml create mode 100644 packages/agentic-fabric/examples/tool_registry.py create mode 100644 packages/agentic-fabric/pyproject.toml create mode 100644 packages/agentic-fabric/src/agentic_fabric/__init__.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/__main__.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/agentic_data.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/base/__init__.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/base/archetypes.yaml create mode 100644 packages/agentic-fabric/src/agentic_fabric/capabilities.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/config/__init__.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/config/agents.yaml create mode 100644 packages/agentic-fabric/src/agentic_fabric/config/llm.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/config/tasks.yaml create mode 100644 packages/agentic-fabric/src/agentic_fabric/core/__init__.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/core/decomposer.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/core/discovery.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/core/loader.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/core/manager.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/core/runner.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/crew.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/crews/__init__.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/__init__.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/config/agents.yaml create mode 100644 packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/config/tasks.yaml create mode 100644 packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/connector_builder_crew.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/main.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/run_flow.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/runners/__init__.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/runners/base.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/runners/local_cli_profiles.yaml create mode 100644 packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/runners/registry.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/runners/single_agent_runner.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/runners/strands_runner.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/tools/__init__.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/tools/adapters.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/tools/registry.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/tools/vendor.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/utils/__init__.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/utils/files.py create mode 100644 packages/agentic-fabric/tests/__init__.py create mode 100644 packages/agentic-fabric/tests/_crew_mocker.py create mode 100644 packages/agentic-fabric/tests/conftest.py create mode 100644 packages/agentic-fabric/tests/e2e/README.md create mode 100644 packages/agentic-fabric/tests/e2e/__init__.py create mode 100644 packages/agentic-fabric/tests/e2e/conftest.py create mode 100644 packages/agentic-fabric/tests/e2e/test_crewai_e2e.py create mode 100644 packages/agentic-fabric/tests/e2e/test_langgraph_e2e.py create mode 100644 packages/agentic-fabric/tests/e2e/test_strands_e2e.py create mode 100644 packages/agentic-fabric/tests/test_agentic_data.py create mode 100644 packages/agentic-fabric/tests/test_capabilities.py create mode 100644 packages/agentic-fabric/tests/test_cli_smoke.py create mode 100644 packages/agentic-fabric/tests/test_connector_builder_crew.py create mode 100644 packages/agentic-fabric/tests/test_crew_mocker.py create mode 100644 packages/agentic-fabric/tests/test_decomposer_edge_cases.py create mode 100644 packages/agentic-fabric/tests/test_discovery.py create mode 100644 packages/agentic-fabric/tests/test_discovery_nested.py create mode 100644 packages/agentic-fabric/tests/test_examples.py create mode 100644 packages/agentic-fabric/tests/test_file_tools.py create mode 100644 packages/agentic-fabric/tests/test_loader.py create mode 100644 packages/agentic-fabric/tests/test_manager.py create mode 100644 packages/agentic-fabric/tests/test_runners.py create mode 100644 packages/agentic-fabric/tests/test_scraping_tools.py create mode 100644 packages/agentic-fabric/tests/test_single_agent_runners.py create mode 100644 packages/agentic-fabric/tests/test_tool_adapters.py create mode 100644 packages/agentic-fabric/tests/test_tool_registry.py create mode 100644 packages/agentic-fabric/tests/test_yaml_config_errors.py create mode 100644 packages/pytest-agentic-fabric/CHANGELOG.md create mode 100644 packages/pytest-agentic-fabric/README.md create mode 100644 packages/pytest-agentic-fabric/pyproject.toml create mode 100644 packages/pytest-agentic-fabric/src/pytest_agentic_fabric/__init__.py create mode 100644 packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py create mode 100644 packages/pytest-agentic-fabric/src/pytest_agentic_fabric/py.typed create mode 100644 packages/pytest-agentic-fabric/tests/test_plugin.py create mode 100644 pyproject.toml create mode 100644 release-please-config.json create mode 100644 tox.ini create mode 100644 uv.lock diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..677ff0b --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,76 @@ +name: CD + +on: + workflow_dispatch: + inputs: + tag: + description: Release tag to publish + required: true + type: string + +permissions: + contents: read + +jobs: + publish: + name: Publish package and docs + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ inputs.tag }} + persist-credentials: false + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: "3.13" + - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + with: + version: "0.11.19" + enable-cache: true + - name: Verify + run: | + uv tool install tox --with tox-uv + tox -e lint,typecheck,py311,py312,py313,py314,plugin,docs,build -- -q + - name: Publish to PyPI + run: uv publish --trusted-publishing always + + pages: + name: Publish docs to GitHub Pages + needs: publish + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + pages: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ inputs.tag }} + persist-credentials: false + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: "3.13" + - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + with: + version: "0.11.19" + enable-cache: true + - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + with: + enablement: true + - name: Build docs + run: | + uv tool install tox --with tox-uv + tox -e docs + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: docs/_build/html + - id: deployment + name: Deploy docs + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e6c44c8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + +jobs: + test: + name: Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - python-version: "3.11" + tox-env: py311 + - python-version: "3.12" + tox-env: py312 + - python-version: "3.13" + tox-env: py313 + - python-version: "3.14" + tox-env: py314 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: ${{ matrix.python-version }} + - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + - name: Run tests + run: uvx --with tox-uv tox -e ${{ matrix.tox-env }} -- -q + + quality: + name: Quality, docs, build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: "3.13" + - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + - name: Run quality gates + run: uvx --with tox-uv tox -e lint,typecheck,plugin,docs,build diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..69c18a0 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,26 @@ +name: CodeQL + +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + schedule: + - cron: "17 6 * * 2" + +permissions: + contents: read + security-events: write + +jobs: + analyze: + name: Analyze Python + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: Initialize CodeQL + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + languages: python + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ffef7f1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,29 @@ +name: Release + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + actions: write + +jobs: + release-please: + name: Release Please + runs-on: ubuntu-latest + steps: + - id: release + uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + - name: Dispatch CD + if: steps.release.outputs.release_created == 'true' + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + TAG_NAME: ${{ steps.release.outputs.tag_name }} + run: gh workflow run cd.yml --repo "$GH_REPO" --ref main -f tag="$TAG_NAME" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..74f2111 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.DS_Store +.coverage +.mypy_cache/ +.pytest_cache/ +.ruff_cache/ +.tox/ +.venv/ +__pycache__/ +build/ +dist/ +htmlcov/ +*.egg-info/ +*.pyc +docs/_build/ +docs/apidocs/ diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..443381e --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,4 @@ +{ + "packages/agentic-fabric": "1.0.0", + "packages/pytest-agentic-fabric": "0.1.0" +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9932a39 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,184 @@ +# Repository Instructions + +This repository contains the `agentic-fabric` Python workspace. + +## Active Goal + +Finish this repository after `extended-data` and `vendor-fabric`. Its immediate +purpose is to hold all agent runtime architecture and make the remaining +migration unambiguous. + +The active work branch is: + +```bash +codex/agentic-fabric-bootstrap +``` + +Use the canonical checkout only: + +```bash +/Users/jbogaty/src/jbcom/agentic-fabric +``` + +Do not create or continue side worktrees for this repo. + +## Related Repositories + +`agentic-fabric` depends on both upstream layers. Periodically check their local +and remote state before finalizing this repo. + +| Layer | Local checkout | Remote | Branch | +| --- | --- | --- | --- | +| Upstream base data | `/Users/jbogaty/src/jbcom/extended-data` | `https://github.com/jbcom/extended-data` | `codex/extended-data-polymorphic-container` | +| Upstream vendor layer | `/Users/jbogaty/src/jbcom/vendor-fabric` | `https://github.com/jbcom/vendor-fabric` | `codex/vendor-fabric-extended-data-boundary` | +| Current repo | `/Users/jbogaty/src/jbcom/agentic-fabric` | `https://github.com/jbcom/agentic-fabric` | `codex/agentic-fabric-bootstrap` | +| Legacy monorepo | `/Users/jbogaty/src/jbcom/extended-data-library` | `https://github.com/jbcom/extended-data-library` | `main` | +| Legacy agent repo | `/Users/jbogaty/src/jbcom/agentic` | `https://github.com/jbcom/agentic` | check locally | + +## Scope + +`agentic-fabric` owns: + +- framework-agnostic crew discovery and loading +- runner adapters for CrewAI, LangGraph, Strands, and local CLI tools +- agent-facing tool resolution and framework adapter utilities +- planned optional passthrough dependencies to `vendor-fabric` after + `vendor-fabric` is published +- `AgenticData` as the agent/runtime-aware extension of `VendorData` +- `pytest-agentic-fabric` for runner, tool, and framework fixture support +- Sphinx/Furo/autodoc2 docs for this workspace + +`extended-data` owns base data primitives, logging, inputs, files, redaction, +and generic workflows. `vendor-fabric` owns vendor connectors, provider-backed +sync, SecretSync for Python, and vendor-backed tools. + +## Architecture Docs + +Read these before changing code: + +- `docs/architecture.rst`: `AgenticData` and agent capability design +- `docs/pillars.rst`: package ownership principles +- `docs/vendor-fabric.rst`: dependency boundary with provider tooling + +Legacy parity sources for this repo are: + +- `/Users/jbogaty/src/jbcom/agentic` +- `/Users/jbogaty/src/jbcom/extended-data-library/packages/vendor-connectors/src/vendor_connectors/agentic` +- `/Users/jbogaty/src/jbcom/vendor-fabric/packages/vendor-fabric/src/vendor_fabric/agentic` +- `/Users/jbogaty/src/jbcom/vendor-fabric/packages/vendor-fabric/tests/agentic` +- `/Users/jbogaty/src/jbcom/vendor-fabric/packages/pytest-vendor-fabric/src/pytest_vendor_fabric/agentic` + +Move agent runtime code here. Leave provider connector code in `vendor-fabric`. +Do not add `vendor-fabric` passthrough extras until `vendor-fabric` exists on +PyPI. The package currently has no resolvable PyPI release under that name. + +## Preferred Commands + +```bash +uv sync --all-packages +tox -e lint +tox -e typecheck +tox -e py311,py312,py313,py314 +tox -e plugin +tox -e docs +tox -e build +``` + +Use `uvx --with tox-uv tox -e ` when tox is not installed locally. + +Do not set `skip_missing_interpreters = true`. Python 3.11, 3.12, 3.13, and +3.14 are all part of the supported release contract. + +## Release Flow + +Releases are managed by release-please: + +- `ci.yml` validates pull requests. +- `release.yml` opens and maintains the release-please PR. +- `cd.yml` publishes packages to PyPI and deploys Sphinx/Furo docs to GitHub + Pages after releases are created. + +Do not hand-edit versions or tags outside release-please work. + +## Finish Criteria + +This repo is not done until: + +1. all agent runtime code from the legacy parity sources is represented here +2. provider connector code remains in `vendor-fabric` +3. `AgenticData` extends the vendor layer and routes agent/runtime capabilities +4. optional framework extras are lazy, discoverable, and documented +5. `pytest-agentic-fabric` is usable and separately publishable +6. docs, examples, tests, and README agree on the public API +7. Python 3.11 through 3.14 pass without skipped missing interpreters +8. a ready pull request is opened and reviewed +9. release-please and CD can publish packages and deploy docs + +## Active Finish Plan + +Use this section as the source of truth for finishing this WIP branch. + +### Current Branch + +```bash +git switch codex/agentic-fabric-bootstrap +``` + +Work only in `/Users/jbogaty/src/jbcom/agentic-fabric`. + +Finish `extended-data` first. Then finish `vendor-fabric`. This package depends +on the final `ExtendedData` and `VendorData` contracts. + +Before final validation, check local and remote state for both upstream repos. + +### Code + +- Keep runtime code under `packages/agentic-fabric`. +- Keep pytest helper code under `packages/pytest-agentic-fabric`. +- Implement `AgenticData` as a `VendorData` subclass with runtime context. +- Implement runner and tool capabilities through decorators collected by + `__init_subclass__`. +- Keep optional framework imports lazy and registry-backed. +- Route vendor-backed tools through `vendor-fabric`; do not call SDKs directly + from agent code. +- Add passthrough `vendor-fabric` extras only after `vendor-fabric` exists on + PyPI. As of this handoff, `vendor-fabric` has no resolvable PyPI release under + that name, so declaring passthrough extras makes `uv lock` fail. +- Use configured logging or Python logging/warnings/exceptions; no runtime + `print()` paths. + +### Docs + +- Keep README, Sphinx guides, examples, and API docs aligned. +- Keep public docs in reStructuredText under `docs/`; do not add authored + Markdown pages there. +- Keep architecture and pillar docs current when architecture changes. +- Document every supported runtime extra and unavailable-runtime behavior. +- Document framework priority and explicit runtime selection. + +### Tests + +- Cover runner registry behavior and capability dispatch. +- Cover optional framework dependencies as unavailable features with install + guidance. +- Cover `AgenticData` active-runtime routing and explicit-runtime routing. +- Keep `pytest-agentic-fabric` tested as its own package. +- Run Python 3.11 through 3.14. Do not skip missing interpreters. + +### GitHub + +- Create the public `jbcom/agentic-fabric` repo if it does not exist. +- Push the active branch. +- Open a ready pull request, not a draft. +- Handle CI failures. +- Inspect and resolve actionable review feedback. +- Squash merge only after the PR is approved and green. +- Confirm release-please and CD can publish packages and deploy docs. + +### Post-Finish AGENTS Cleanup + +After this finish plan is complete and the PR is merged, compress this file to +durable repository guidance only. Remove branch-specific WIP instructions, +legacy migration checklist items, and temporary hand-off details. Keep the +canonical package scope, normal validation commands, release flow notes, and +stable downstream/upstream boundaries. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0de494e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 jbcom + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..95ff2f9 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Agentic Fabric Workspace + +This repository is the future public `jbcom/agentic-fabric` workspace. + +Documentation: [jonbogaty.com/agentic-fabric](https://jonbogaty.com/agentic-fabric/) + +Read `AGENTS.md` first in Codex sessions; it contains the active local +migration plan and hand-off instructions. + +The active branch is `codex/agentic-fabric-bootstrap`. This repository should +be completed after `extended-data` and `vendor-fabric`, because its +`AgenticData` design depends on their final contracts. + +Current implemented surface includes `AgenticData`, lazy runtime registry +metadata, capability decorators, vendor-tool references, and the sibling +`pytest-agentic-fabric` package. diff --git a/docs/_static/agentic-crew.css b/docs/_static/agentic-crew.css new file mode 100644 index 0000000..6eea12c --- /dev/null +++ b/docs/_static/agentic-crew.css @@ -0,0 +1 @@ +/* Placeholder for project-specific docs styling. */ diff --git a/docs/agentic-workflows.rst b/docs/agentic-workflows.rst new file mode 100644 index 0000000..253d15c --- /dev/null +++ b/docs/agentic-workflows.rst @@ -0,0 +1,41 @@ +Agentic Workflows +================= + +Crew definitions are YAML-first and runtime-neutral unless placed in a +framework-specific directory. + +.. code:: yaml + + name: example + crews: + reviewer: + description: Review implementation quality + agents: config/agents.yaml + tasks: config/tasks.yaml + +``agentic-fabric`` resolves the crew, selects a runner, and delegates +execution to the installed framework. The public API is intentionally +small: + +.. code:: python + + from agentic_fabric import discover_packages, get_crew_config, run_crew_auto + + packages = discover_packages() + config = get_crew_config(packages["example"], "reviewer") + result = run_crew_auto(config, inputs={"topic": "release readiness"}) + +For stateful orchestration, register the same config with +``AgenticData``: + +.. code:: python + + from agentic_fabric import AgenticData + + session = AgenticData(agent_registry={"reviewer": config}) + session.use_runtime("langgraph", strict=False) + result = session.run_reviewer({"topic": "release readiness"}) + +When no framework is installed, discovery and metadata inspection still +work. Execution reports the unavailable runtime instead of failing +during import. diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 0000000..708ed11 --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,10 @@ +API Reference +============= + +This section is generated from ``src/agentic_fabric`` with +``sphinx-autodoc2``. + +.. toctree:: + :maxdepth: 2 + + ../apidocs/index diff --git a/docs/architecture.rst b/docs/architecture.rst new file mode 100644 index 0000000..641011e --- /dev/null +++ b/docs/architecture.rst @@ -0,0 +1,129 @@ +Architecture Plan +================= + +This page records the local plan for ``agentic-fabric``. It is the +source of truth for agent runtime architecture in this repository. + +Boundary +-------- + +``agentic-fabric`` owns agent and crew orchestration: + +- discovery and manifest loading +- universal crew decomposition +- runner adapters for optional agent frameworks +- tool resolution for built-in, MCP-style, fully qualified, and + vendor-backed tools +- agent capability dispatch over the data and vendor layers +- pytest support for agent runtime tests + +``extended-data`` owns base data primitives, logging, input handling, +files, redaction, and generic workflows. ``vendor-fabric`` owns vendor +API connectors, provider-backed sync, SecretSync for Python, and +vendor-backed tool adapters. + +AgenticData +----------- + +``AgenticData`` extends the vendor layer in the same way ``VendorData`` +extends ``ExtendedData``. The full-stack inheritance line is: + +.. code:: text + + ExtendedData -> VendorData -> AgenticData + +``AgenticData`` owns agent runtime context. It does not reimplement data +containers or provider sync. Those behaviors come from inherited layers +when ``vendor-fabric`` is installed, and from an import-safe fallback +otherwise. + +.. code:: python + + from agentic_fabric import AgenticData + + + session = AgenticData({"task": "summarize"}) + session.use_runtime("langgraph", strict=False) + +That shape lets the agent layer carry data, provider, and runtime context +together: + +.. code:: python + + session.register_agent("repo_summarizer", crew_config) + result = session.run_agent("repo_summarizer", repo="jbcom/extended-data") + +Agent Capability Registry +------------------------- + +Agent and runtime capabilities should use normal Python mechanisms: + +- ``typing.Protocol`` for runner and tool contracts +- abstract base classes for shared runner behavior +- ``@agent_capability(...)`` or ``@tool_capability(...)`` decorators on + concrete runtime/tool methods +- ``__init_subclass__`` to collect capability metadata through + inheritance +- read-only capability mappings for inspection and deterministic + dispatch +- ``__getattr__`` only as a convenience over declared capabilities +- ``__dir__`` for autocomplete and developer inspection + +The implementation lives in ``agentic_fabric.capabilities``: + +- ``@agent_capability(...)`` +- ``@runtime_capability(...)`` +- ``@tool_capability(...)`` +- ``AgentCapabilityProviderMixin`` + +Do not use custom dunder capability names. The capability registry is +plain Python metadata collected at class creation. + +Runtime Selection +----------------- + +Runtime frameworks are optional and should be discovered lazily. The +core package should import without CrewAI, LangGraph, Strands, or vendor +SDKs being imported eagerly. + +Runtime metadata lives in ``agentic_fabric.runners.registry``. The public +compatibility API remains in ``agentic_fabric.core.decomposer``: +``detect_framework()``, ``get_runner()``, ``get_available_frameworks()``, +and ``get_framework_info()``. + +Runtime selection should follow explicit precedence when the caller does +not choose a runtime: + +1. a runtime already active on ``AgenticData`` +2. a runtime requested in the crew manifest +3. the first installed runtime in documented priority order +4. a clear unavailable-runtime error with install guidance + +Vendor Tooling +-------------- + +Vendor-backed tools should go through ``AgenticData``, ``VendorData``, +and ``vendor-fabric`` capabilities. Agent code should not call cloud SDKs +directly. Agent tools that need repository files, Slack messages, Vault +secrets, or S3 objects should route through provider capabilities +declared by ``vendor-fabric``. + +Lazy vendor tool references are supported with +``vendor://provider/operation`` and ``vendor:provider:operation`` names. + +Testing Package +--------------- + +``pytest-agentic-fabric`` is a sibling package in this repository. It +provides runtime availability fixtures, runtime module mocking, minimal +crew/workspace fixtures, and opt-in E2E controls. + +Provider fixtures belong in ``pytest-vendor-fabric``. Base data fixtures +belong in ``pytest-extended-data``. + +Validation Contract +------------------- + +Every public behavior needs tests and docs in the same change. Python +3.11, 3.12, 3.13, and 3.14 must pass without skipped missing +interpreters. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..96e0bc9 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,35 @@ +"""Sphinx configuration for agentic-fabric.""" + +from __future__ import annotations + + +project = "Agentic Fabric" +author = "Jon Bogaty" +copyright = "2026, Jon Bogaty" # noqa: A001 + +extensions = [ + "myst_parser", + "autodoc2", + "sphinx_copybutton", +] + +source_suffix = { + ".rst": "restructuredtext", +} + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] +html_theme = "furo" +html_static_path = ["_static"] +html_css_files = ["agentic-fabric.css"] + +autodoc2_packages = [ + { + "path": "../packages/agentic-fabric/src/agentic_fabric", + "module": "agentic_fabric", + } +] +autodoc2_output_dir = "apidocs" +autodoc2_render_plugin = "rst" +autodoc2_docstring_parser_regexes = [(r".*", "myst")] +autodoc2_hidden_objects = ["dunder", "private"] diff --git a/docs/development.rst b/docs/development.rst new file mode 100644 index 0000000..4d7aec8 --- /dev/null +++ b/docs/development.rst @@ -0,0 +1,29 @@ +Development +=========== + +Use tox for local validation: + +.. code:: bash + + tox -e lint + tox -e typecheck + tox -e py311 + tox -e py312 + tox -e py313 + tox -e py314 + tox -e docs + tox -e build + +Optional framework and vendor tests should be marked and should skip +cleanly when the relevant extra is not installed. + +Use ``pytest-agentic-fabric`` fixtures for reusable runtime test setup: + +.. code:: python + + def test_runtime(agentic_mock_runtime, agentic_crew_config): + agentic_mock_runtime("langgraph") + assert agentic_crew_config["name"] == "test_crew" + +Before deleting old monorepo code, first prove this repository owns the +moved surface with docs, tests, package metadata, and release workflows. diff --git a/docs/getting-started.rst b/docs/getting-started.rst new file mode 100644 index 0000000..14422a4 --- /dev/null +++ b/docs/getting-started.rst @@ -0,0 +1,41 @@ +Getting Started +=============== + +Install the core package when you only need discovery, configuration +loading, and lazy runner selection: + +.. code:: bash + + pip install agentic-fabric + +Install the runtime extras you actually use: + +.. code:: bash + + pip install "agentic-fabric[crewai]" + pip install "agentic-fabric[langgraph]" + pip install "agentic-fabric[strands]" + +.. code:: python + + from agentic_fabric import detect_framework, get_framework_info, get_runner + + runtimes = get_framework_info() + framework = detect_framework() + runner = get_runner(framework) + +Crew definitions can live in ``.crew/``, ``.crewai/``, ``.langgraph/``, +or ``.strands/`` directories. A ``.crew/`` directory is +framework-agnostic. A framework-specific directory requires that +runtime. + +Use ``AgenticData`` when runtime context should travel with data and +registered crew definitions: + +.. code:: python + + from agentic_fabric import AgenticData + + session = AgenticData({"repo": "jbcom/agentic-fabric"}) + session.register_agent("reviewer", crew_config) + result = session.run_agent("reviewer", runtime="crewai") diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..99d7f1b --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,32 @@ +Agentic Fabric +============== + +``agentic-fabric`` is the standalone orchestration package for +framework-agnostic agent crews. It discovers YAML crew definitions, +selects an installed runtime, and runs the same crew on CrewAI, +LangGraph, Strands, or local CLI runners. + +.. code:: bash + + pip install agentic-fabric + pip install "agentic-fabric[crewai]" + pip install "agentic-fabric[langgraph]" + pip install "agentic-fabric[strands]" + +Vendor-backed tools will be exposed as passthrough extras after +``vendor-fabric`` is available on PyPI. Until then, agent runtime work +stays separate from provider dependency declarations. + +Core imports stay lightweight. Optional frameworks and vendor SDKs are +loaded only when a runner, tool, or adapter is resolved. + +.. toctree:: + :maxdepth: 2 + + getting-started + architecture + agentic-workflows + vendor-fabric + pillars + development + api/index diff --git a/docs/pillars.rst b/docs/pillars.rst new file mode 100644 index 0000000..9cebf5b --- /dev/null +++ b/docs/pillars.rst @@ -0,0 +1,52 @@ +Pillars +======= + +Declare Once +------------ + +Crew definitions should be portable across supported runtimes. Framework +specificity belongs in adapters and optional extras, not in the basic +crew schema. + +Data And Context Move Together +------------------------------ + +``AgenticData`` should carry the current extended data value, active +provider context, active runtime context, logging context, and +tool/capability registry. It should extend ``VendorData``; it should not +duplicate ``ExtendedData`` or provider behavior. + +Lazy by Default +--------------- + +Optional frameworks, vendor SDKs, and tool packages must be imported +only when their runner or tool is used. + +Capabilities Over Boilerplate +----------------------------- + +Agent runners and tools should declare capabilities with decorators +collected by ``__init_subclass__``. Dynamic facade methods are allowed +only when they route to declared capabilities. + +Clear Boundaries +---------------- + +This package does not own data primitives or vendor APIs. It composes +``extended-data`` and ``vendor-fabric`` when those capabilities are +installed. + +Testable Adapters +----------------- + +Every runner and tool adapter should have unit coverage for resolution, +availability, and failure behavior. End-to-end tests can stay optional +and marked by runtime. + +Frameworks Are Optional, Contracts Are Not +------------------------------------------ + +CrewAI, LangGraph, Strands, and local CLI runners can be optional +installs. Their availability, missing dependency messages, fixture +behavior, and runtime selection order must still be documented and +tested. diff --git a/docs/vendor-fabric.rst b/docs/vendor-fabric.rst new file mode 100644 index 0000000..92fe961 --- /dev/null +++ b/docs/vendor-fabric.rst @@ -0,0 +1,27 @@ +Vendor Fabric Integration +========================= + +``agentic-fabric`` does not declare passthrough ``vendor-fabric`` extras +until ``vendor-fabric`` is published and resolvable from PyPI. The core +package still imports without vendor SDKs installed. + +The integration rule is simple: vendor IO and SecretSync behavior belong +in ``vendor-fabric``; crew orchestration and runtime selection belong +here. + +``AgenticData`` subclasses ``VendorData`` when ``vendor-fabric`` is +installed. Until then, it keeps runtime context importable and raises +clear guidance for vendor-backed operations. + +Vendor-backed tools use lazy references: + +.. code:: python + + from agentic_fabric.tools.registry import resolve_tool + + tool = resolve_tool("vendor://github/get_file") + result = tool(path="README.md") + +Those wrappers route through ``AgenticData.call`` and ``VendorData`` +capabilities. Agent code should not import cloud SDKs or provider +clients directly. diff --git a/packages/agentic-fabric/CHANGELOG.md b/packages/agentic-fabric/CHANGELOG.md new file mode 100644 index 0000000..acbe9b7 --- /dev/null +++ b/packages/agentic-fabric/CHANGELOG.md @@ -0,0 +1,39 @@ +# CHANGELOG + + + +## v1.0.0 (2025-12-25) + +### Initial Stable Release + +Framework-agnostic AI crew orchestration is now production-ready! + +### Features + +- **Multi-Framework Support**: Run crews on CrewAI, LangGraph, or Strands +- **Auto-Detection**: Automatically selects best available framework +- **Universal YAML Format**: Define once, run anywhere +- **Single-Agent CLI Runners**: Support for aider, claude-code, ollama, and more +- **Knowledge Base Integration**: Load domain knowledge from markdown files +- **Agent Archetypes**: Reusable agent templates +- **Comprehensive Testing**: 122+ unit tests, E2E test suite + +### Breaking Changes + +- **Minimum Python version increased from 3.10 to 3.11** + - Required for CrewAI 1.5.0+ compatibility + - Python 3.11, 3.12, 3.13, 3.14 now supported + +### Documentation + +- Complete API documentation with Sphinx +- Quick start guide and architecture overview +- jbcom dark theme branding applied +- Integration guides for vendor-fabric + +### Internal + +- Full test coverage across all runners +- Ruff linting and formatting +- Type hints with mypy +- Tox for multi-version testing diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md new file mode 100644 index 0000000..6fcc248 --- /dev/null +++ b/packages/agentic-fabric/README.md @@ -0,0 +1,153 @@ +# agentic-fabric + +[![PyPI version](https://img.shields.io/pypi/v/agentic-fabric.svg)](https://pypi.org/project/agentic-fabric/) +[![CI](https://github.com/jbcom/agentic-fabric/actions/workflows/ci.yml/badge.svg)](https://github.com/jbcom/agentic-fabric/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Python](https://img.shields.io/pypi/pyversions/agentic-fabric.svg)](https://pypi.org/project/agentic-fabric/) + +Framework-agnostic agent crew orchestration. Declare crews once in YAML, then +run them on CrewAI, LangGraph, Strands, or local CLI runners without changing a +single crew definition. Runtime frameworks are optional and are detected lazily +from what is installed. + +[Documentation](https://jonbogaty.com/agentic-fabric/) | [Source](https://github.com/jbcom/agentic-fabric) | [Issues](https://github.com/jbcom/agentic-fabric/issues) + +## Installation + +```bash +# Core discovery, loading, and runner selection +pip install agentic-fabric + +# With a specific framework +pip install "agentic-fabric[crewai]" +pip install "agentic-fabric[langgraph]" +pip install "agentic-fabric[strands]" + +# All frameworks +pip install "agentic-fabric[ai]" + +# Vendor-backed passthrough extras are added after vendor-fabric is published. +``` + +## Quick Start + +### 1. Define a Crew (YAML) + +```yaml +# .crewai/crews/analyzer/agents.yaml +code_reviewer: + role: Senior Code Reviewer + goal: Find bugs and improvements + backstory: Expert at code analysis +``` + +```yaml +# .crewai/crews/analyzer/tasks.yaml +review_code: + description: Review the provided code for issues + expected_output: List of findings with severity + agent: code_reviewer +``` + +### 2. Run It + +```python +from agentic_fabric import run_crew_auto, detect_framework + +# See what framework is available +framework = detect_framework() + +# Auto-detect best framework and run +result = run_crew_auto(config, inputs={"code": "..."}) +``` + +Or from the CLI: + +```bash +agentic-fabric run my-package analyzer --input "Review this code: ..." +``` + +### 3. Use a Specific Runner + +```python +from agentic_fabric import get_runner + +runner = get_runner("langgraph") # Force LangGraph +crew = runner.build_crew(config) +result = runner.run(crew, inputs) +``` + +### 4. Carry Runtime Context with Data + +```python +from agentic_fabric import AgenticData, get_framework_info + +print(get_framework_info()) + +session = AgenticData({"repo": "jbcom/agentic-fabric"}) +session.register_agent("reviewer", config) +result = session.run_agent("reviewer", runtime="crewai") +``` + +## Key Features + +- Framework agnostic: one crew definition, multiple runtime backends. +- Lazy imports: core package import does not require CrewAI, LangGraph, + Strands, or vendor SDKs. +- `AgenticData`: carries data, registered crews, active runtime selection, and + vendor-layer context together. +- Capability decorators: runners and tools expose declared capabilities through + read-only metadata and deterministic dispatch. +- YAML-first: crew configuration in YAML, not Python boilerplate. +- Hierarchical orchestration: `ManagerAgent` delegates across crews. +- Package discovery: finds `.crew/`, `.crewai/`, `.langgraph/`, and + `.strands/` directories. +- Vendor passthrough extras are planned after `vendor-fabric` is published to + PyPI. +- CLI and library: use from the command line or import as a module. + +## Framework Priority + +1. CrewAI, if installed. +2. LangGraph, if CrewAI is unavailable. +3. Strands, if neither CrewAI nor LangGraph is available. + +You can always force a specific runner with `get_runner("langgraph")` or +`agentic-fabric run --framework langgraph`. + +## Repository Boundary + +- `extended-data` owns base data containers, logging, input handling, files, + redaction, and generic workflows. +- `vendor-fabric` owns vendor connectors, provider-backed sync, SecretSync for + Python, and vendor-backed tool adapters. +- `agentic-fabric` owns crew discovery, runner selection, framework adapters, and + orchestration. + +Full guides and API documentation are published at +[jonbogaty.com/agentic-fabric](https://jonbogaty.com/agentic-fabric/). +`AGENTS.md` contains the active local migration plan for Codex sessions. + +## Documentation + +The docs are built with Sphinx, Furo, and sphinx-autodoc2: + +```bash +tox -e docs +``` + +Local validation: + +```bash +tox -e lint +tox -e typecheck +tox -e py311 +tox -e py312 +tox -e py313 +tox -e py314 +tox -e build +``` + +## License + +MIT diff --git a/packages/agentic-fabric/examples/README.md b/packages/agentic-fabric/examples/README.md new file mode 100644 index 0000000..4453859 --- /dev/null +++ b/packages/agentic-fabric/examples/README.md @@ -0,0 +1,14 @@ +# Examples + +These examples are intentionally executable in CI. They avoid live LLM calls +and optional framework imports unless explicitly noted, so they also serve as +integration checks for the public API. + +```bash +python examples/discovery_workflow.py +python examples/tool_registry.py +python examples/runtime_context.py +``` + +The bundled `sample_workspace/` directory demonstrates a framework-agnostic +`.crew/` layout. diff --git a/packages/agentic-fabric/examples/__init__.py b/packages/agentic-fabric/examples/__init__.py new file mode 100644 index 0000000..288b1ae --- /dev/null +++ b/packages/agentic-fabric/examples/__init__.py @@ -0,0 +1 @@ +"""Runnable agentic-fabric examples.""" diff --git a/packages/agentic-fabric/examples/discovery_workflow.py b/packages/agentic-fabric/examples/discovery_workflow.py new file mode 100644 index 0000000..fb33473 --- /dev/null +++ b/packages/agentic-fabric/examples/discovery_workflow.py @@ -0,0 +1,47 @@ +"""Discover and inspect a framework-agnostic crew workspace.""" + +from __future__ import annotations + +import json + +from pathlib import Path +from typing import Any + +from agentic_fabric.core.discovery import discover_packages, get_crew_config, load_manifest + + +DEFAULT_WORKSPACE = Path(__file__).parent / "sample_workspace" + + +def summarize_workspace(workspace_root: Path = DEFAULT_WORKSPACE) -> dict[str, Any]: + """Return a deterministic summary of crews in a workspace.""" + packages = discover_packages(workspace_root=workspace_root) + summary: dict[str, Any] = {"workspace": str(workspace_root), "packages": {}} + + for package_name, config_dir in sorted(packages.items()): + manifest = load_manifest(config_dir) + crew_summaries = {} + for crew_name in sorted(manifest.get("crews", {})): + crew_config = get_crew_config(config_dir, crew_name) + crew_summaries[crew_name] = { + "description": crew_config.get("description", ""), + "required_framework": crew_config.get("required_framework"), + "agents": sorted(crew_config.get("agents", {})), + "tasks": sorted(crew_config.get("tasks", {})), + } + + summary["packages"][package_name] = { + "config_dir": config_dir.name, + "crews": crew_summaries, + } + + return summary + + +def main() -> None: + """Print the sample workspace summary as JSON.""" + print(json.dumps(summarize_workspace(), indent=2, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/packages/agentic-fabric/examples/runtime_context.py b/packages/agentic-fabric/examples/runtime_context.py new file mode 100644 index 0000000..ac53ae3 --- /dev/null +++ b/packages/agentic-fabric/examples/runtime_context.py @@ -0,0 +1,27 @@ +"""Inspect AgenticData runtime context without requiring optional frameworks.""" + +from __future__ import annotations + +import json + +from typing import Any + +from agentic_fabric import AgenticData, get_framework_info + + +def inspect_runtime_context() -> dict[str, Any]: + """Return a serializable summary of runtime context behavior.""" + data = AgenticData({"task": "review"}) + data.register_agent("reviewer", {"name": "reviewer", "agents": {}, "tasks": {}}) + data.use_runtime("crewai", strict=False) + + return { + "active_runtime": data.active_runtime, + "registered_agents": sorted(data.agent_registry), + "runtime_names": [item["name"] for item in get_framework_info()], + "vendor_fabric_available": data.vendor_fabric_available, + } + + +if __name__ == "__main__": + print(json.dumps(inspect_runtime_context(), indent=2)) diff --git a/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/crews/implementation_review/agents.yaml b/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/crews/implementation_review/agents.yaml new file mode 100644 index 0000000..2314467 --- /dev/null +++ b/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/crews/implementation_review/agents.yaml @@ -0,0 +1,9 @@ +reviewer: + role: Senior Python reviewer + goal: Identify correctness, packaging, and migration risks. + backstory: Experienced Python library maintainer focused on testable APIs. + +documenter: + role: Documentation reviewer + goal: Ensure examples, README content, and API docs match implementation behavior. + backstory: Maintains developer-facing documentation for reusable Python packages. diff --git a/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/crews/implementation_review/tasks.yaml b/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/crews/implementation_review/tasks.yaml new file mode 100644 index 0000000..ef8d002 --- /dev/null +++ b/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/crews/implementation_review/tasks.yaml @@ -0,0 +1,9 @@ +review_code: + description: Review the submitted implementation for defects and migration gaps. + expected_output: Findings with severity, file references, and suggested fixes. + agent: reviewer + +review_docs: + description: Confirm documentation and examples describe the implementation accurately. + expected_output: Documentation alignment notes and missing examples. + agent: documenter diff --git a/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/manifest.yaml b/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/manifest.yaml new file mode 100644 index 0000000..a682c87 --- /dev/null +++ b/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/manifest.yaml @@ -0,0 +1,7 @@ +name: review +description: Framework-agnostic review crew example. +crews: + implementation_review: + description: Review an implementation for correctness and migration readiness. + agents: crews/implementation_review/agents.yaml + tasks: crews/implementation_review/tasks.yaml diff --git a/packages/agentic-fabric/examples/tool_registry.py b/packages/agentic-fabric/examples/tool_registry.py new file mode 100644 index 0000000..a4521c4 --- /dev/null +++ b/packages/agentic-fabric/examples/tool_registry.py @@ -0,0 +1,29 @@ +"""Resolve tools without importing optional framework packages.""" + +from __future__ import annotations + +import json + +from typing import Any + +from agentic_fabric.tools.registry import resolve_tool + + +def inspect_registry() -> dict[str, Any]: + """Return deterministic registry resolution examples.""" + framework_dirs = resolve_tool("agentic_fabric.core.discovery:FRAMEWORK_DIRS") + unresolved_mcp = resolve_tool("mcp://git/execute_command") + + return { + "framework_dirs": framework_dirs, + "mcp_git_available": unresolved_mcp is not None, + } + + +def main() -> None: + """Print registry inspection as JSON.""" + print(json.dumps(inspect_registry(), indent=2, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/packages/agentic-fabric/pyproject.toml b/packages/agentic-fabric/pyproject.toml new file mode 100644 index 0000000..305a663 --- /dev/null +++ b/packages/agentic-fabric/pyproject.toml @@ -0,0 +1,201 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "agentic-fabric" +version = "1.0.0" +description = "Framework-agnostic agent crew orchestration for CrewAI, LangGraph, Strands, local CLI runners, and vendor-fabric tools" +requires-python = ">=3.11" +license = { text = "MIT" } +readme = "README.md" +authors = [{ name = "Jon Bogaty", email = "jon@jonbogaty.com" }] +maintainers = [{ name = "Jon Bogaty", email = "jon@jonbogaty.com" }] +keywords = ["crewai", "langgraph", "strands", "ai-agents", "multi-agent", "orchestration", "vendor-fabric"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Typing :: Typed", +] +dependencies = [ + "pydantic>=2.13.4,<3.0.0", + "pyyaml>=6.0.3", +] + +[project.optional-dependencies] +crewai = ["crewai[tools,anthropic]>=1.5.0"] +langgraph = ["langgraph>=0.2.0", "langchain-anthropic>=0.3.0"] +strands = ["strands-agents>=0.1.0"] +ai = [ + "crewai[tools,anthropic]>=1.5.0", + "langgraph>=0.2.0", + "langchain-anthropic>=0.3.0", + "strands-agents>=0.1.0", +] +mcp = ["mcp>=1.0.0"] +scraping = ["crewai[tools]>=1.5.0", "beautifulsoup4>=4.12.0", "requests>=2.31.0"] +tests = [ + "coverage[toml]>=7.6.0", + "pytest-asyncio>=0.23.0", + "pytest-cov>=7.1.0", + "pytest-mock>=3.15.1", + "pytest>=9.0.3", + "pytest-timeout>=2.4.0", + "pytest-xdist>=3.6.1", +] +typing = [ + "mypy>=1.20.1", + "types-PyYAML>=6.0.12.20240724", + "types-requests>=2.33.0.20260408", +] +dev = [ + "agentic-fabric[tests,typing]", + "ruff>=0.8.0", +] +docs = [ + "furo>=2025.12.19", + "myst-parser>=4.0.1,<6.0.0", + "sphinx>=8.2.3,<9.0.0", + "sphinx-autodoc2>=0.5.0", + "sphinx-copybutton>=0.5.2", +] +all = [ + "crewai[tools,anthropic]>=1.5.0", + "langgraph>=0.2.0", + "langchain-anthropic>=0.3.0", + "strands-agents>=0.1.0", + "mcp>=1.0.0", + "beautifulsoup4>=4.12.0", + "requests>=2.31.0", +] + +[project.scripts] +agentic-fabric = "agentic_fabric.main:main" +run-crew = "agentic_fabric.crew:kickoff" +train-crew = "agentic_fabric.crew:train" + +[project.urls] +Documentation = "https://jonbogaty.com/agentic-fabric/" +Issues = "https://github.com/jbcom/agentic-fabric/issues" +Source = "https://github.com/jbcom/agentic-fabric" +Changelog = "https://github.com/jbcom/agentic-fabric/blob/main/CHANGELOG.md" + +[tool.hatch.build.targets.wheel] +packages = ["src/agentic_fabric"] + +[tool.pytest.ini_options] +addopts = ["-ra", "--strict-markers", "--strict-config"] +testpaths = ["tests"] +xfail_strict = true +asyncio_mode = "auto" +timeout = 30 +timeout_method = "thread" +markers = [ + "crewai: tests that require CrewAI runtime dependencies", + "e2e: end-to-end tests that require external runtime dependencies", + "langgraph: tests that require LangGraph runtime dependencies", + "strands: tests that require Strands runtime dependencies", + "vendor_fabric: tests that require vendor-fabric runtime dependencies", +] + +[tool.coverage.run] +branch = true +parallel = true +source = ["agentic_fabric"] + +[tool.coverage.report] +show_missing = true +skip_covered = true + +[tool.ruff] +target-version = "py311" +line-length = 120 +src = ["src", "tests", "examples"] + +[tool.ruff.lint] +select = ["ALL"] +ignore = [ + "ANN", + "ARG", + "BLE001", + "C901", + "COM", + "D105", + "D205", + "D415", + "D417", + "DTZ007", + "E402", + "E501", + "EM", + "ERA001", + "FBT", + "FIX", + "G", + "PERF", + "PLC0415", + "PLR0124", + "PLR0911", + "PLR0912", + "PLR0913", + "PLR0915", + "PLR2004", + "PLW0603", + "PTH", + "RET505", + "RUF012", + "S105", + "S110", + "S310", + "S603", + "SLF001", + "TCH", + "TD", + "TID252", + "TRY003", + "TRY300", + "TRY301", + "TRY401", +] + +[tool.ruff.lint.per-file-ignores] +"examples/**" = ["T201"] +"tests/**" = ["B018", "BLE", "D", "DTZ", "INP001", "LOG001", "N802", "N803", "N806", "PLC0415", "PT", "S", "SIM117", "TRY"] +"src/agentic_fabric/main.py" = ["T201"] +"src/agentic_fabric/run_flow.py" = ["T201"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.isort] +lines-between-types = 1 +lines-after-imports = 2 + +[tool.mypy] +python_version = "3.11" +pretty = true +show_error_codes = true +ignore_missing_imports = true +warn_return_any = false +warn_unused_configs = true +exclude = ["tests/e2e/.*", "tests/.*/conftest.py"] + +[[tool.mypy.overrides]] +module = [ + "bs4", + "crewai", + "crewai.*", + "crewai_tools", + "langchain_anthropic", + "langgraph", + "langgraph.*", + "mesh_toolkit.*", + "strands", +] +ignore_missing_imports = true diff --git a/packages/agentic-fabric/src/agentic_fabric/__init__.py b/packages/agentic-fabric/src/agentic_fabric/__init__.py new file mode 100644 index 0000000..1653ed9 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/__init__.py @@ -0,0 +1,85 @@ +"""agentic-fabric: Framework-agnostic AI crew orchestration. + +Declare crews once, run on CrewAI, LangGraph, or Strands. + +Usage: + from agentic_fabric.core.decomposer import run_crew_auto, get_runner, detect_framework + from agentic_fabric.core.discovery import discover_packages, get_crew_config + from agentic_fabric.core.manager import ManagerAgent + + # Auto-detect framework and run a crew + packages = discover_packages() + config = get_crew_config(packages["my-package"], "my_crew") + result = run_crew_auto(config, inputs={"task": "..."}) + + # Or get a specific runner + runner = get_runner("crewai") # or "langgraph", "strands" + crew = runner.build_crew(config) + result = runner.run(crew, inputs) + + # Or use a hierarchical manager agent + class MyManager(ManagerAgent): + def __init__(self): + super().__init__(crews={ + "design": "design_crew", + "implementation": "impl_crew" + }) + + async def execute_workflow(self, task): + design = await self.delegate_async("design", task) + return await self.delegate_async("implementation", design) +""" + +from __future__ import annotations + + +__version__ = "1.0.0" + +# Core exports - framework-agnostic functionality +from agentic_fabric.agentic_data import AgenticData +from agentic_fabric.capabilities import ( + AgentCapabilityProviderMixin, + AgentCapabilitySpec, + agent_capability, + runtime_capability, + tool_capability, +) +from agentic_fabric.core.decomposer import ( + decompose_crew, + detect_framework, + get_available_frameworks, + get_framework_info, + get_runner, + is_framework_available, + run_crew_auto, +) +from agentic_fabric.core.discovery import ( + discover_all_framework_configs, + discover_packages, + get_crew_config, + list_crews, +) +from agentic_fabric.core.manager import ManagerAgent + + +__all__ = [ + "AgentCapabilityProviderMixin", + "AgentCapabilitySpec", + "AgenticData", + "ManagerAgent", + "__version__", + "agent_capability", + "decompose_crew", + "detect_framework", + "discover_all_framework_configs", + "discover_packages", + "get_available_frameworks", + "get_crew_config", + "get_framework_info", + "get_runner", + "is_framework_available", + "list_crews", + "run_crew_auto", + "runtime_capability", + "tool_capability", +] diff --git a/packages/agentic-fabric/src/agentic_fabric/__main__.py b/packages/agentic-fabric/src/agentic_fabric/__main__.py new file mode 100644 index 0000000..596e6e4 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/__main__.py @@ -0,0 +1,9 @@ +"""Allow running as: python -m crew_agents""" + +from __future__ import annotations + +from agentic_fabric.main import main + + +if __name__ == "__main__": + main() diff --git a/packages/agentic-fabric/src/agentic_fabric/agentic_data.py b/packages/agentic-fabric/src/agentic_fabric/agentic_data.py new file mode 100644 index 0000000..1be8ccc --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/agentic_data.py @@ -0,0 +1,264 @@ +"""Agent runtime facade over the vendor and data layers.""" + +from __future__ import annotations + +from collections.abc import Mapping +from types import MappingProxyType +from typing import Any, ClassVar + +from agentic_fabric.runners.registry import RuntimeUnavailableError, install_command, runtime_info, runtime_names + + +try: # pragma: no cover - exercised when vendor-fabric is installed by consumers + from vendor_fabric.vendor_data import VendorData as _VendorDataBase +except ImportError: # pragma: no cover - default in this workspace until vendor-fabric is published + _VENDOR_FABRIC_AVAILABLE = False + + class _VendorDataBase: # type: ignore[no-redef] + """Small fallback that keeps AgenticData importable without vendor-fabric.""" + + def __init__(self, value: Any = None, **_: Any) -> None: + self._agentic_value = value + self._active_provider: str | None = None + + @property + def value(self) -> Any: + """Return the wrapped value.""" + return self._agentic_value + + @property + def active_provider(self) -> str | None: + """Return the active vendor provider, when available.""" + return self._active_provider + + def as_builtin(self) -> Any: + """Return the wrapped value unchanged.""" + return self._agentic_value + + def cast(self, value: Any) -> _VendorDataBase: + """Replace the wrapped value.""" + self._agentic_value = value + return self + + def open(self, provider_id: str, *, strict: bool = True, **_: Any) -> _VendorDataBase: + """Record a provider or raise install guidance when strict.""" + if strict: + msg = ( + f"Provider '{provider_id}' requires vendor-fabric. " + "Install vendor-fabric after it is published, then reinstall agentic-fabric." + ) + raise ImportError(msg) + self._active_provider = provider_id + return self + + def call(self, operation: str, *_: Any, **__: Any) -> Any: + """Raise clear guidance for vendor-backed operations.""" + msg = f"Vendor operation '{operation}' requires vendor-fabric." + raise ImportError(msg) + +else: + _VENDOR_FABRIC_AVAILABLE = True + + +class AgenticData(_VendorDataBase): + """VendorData extension with active runtime and agent registry context.""" + + runtime_priority: ClassVar[tuple[str, ...]] = tuple(runtime_names()) + + def __init__( + self, + value: Any = None, + *, + fabric: Any | None = None, + agent_registry: Mapping[str, Mapping[str, Any]] | None = None, + logger: Any | None = None, + active_runtime: str | None = None, + **fabric_kwargs: Any, + ) -> None: + """Initialize data, registered crews, and optional active runtime.""" + super().__init__(value, fabric=fabric, logger=logger, **fabric_kwargs) + self._agent_registry: dict[str, dict[str, Any]] = { + name: dict(config) for name, config in (agent_registry or {}).items() + } + self._active_runtime: str | None = None + if active_runtime is not None: + self.use_runtime(active_runtime, strict=False) + + @property + def agent_registry(self) -> Mapping[str, Mapping[str, Any]]: + """Return registered agent/crew definitions.""" + return MappingProxyType(self._agent_registry) + + @property + def active_runtime(self) -> str | None: + """Return the active runtime name, when one has been selected.""" + return self._active_runtime + + @property + def vendor_fabric_available(self) -> bool: + """Return whether this import is backed by a real VendorData class.""" + return _VENDOR_FABRIC_AVAILABLE + + def cast(self, value: Any) -> AgenticData: + """Mutate the wrapped data while preserving runtime context.""" + super().cast(value) + return self + + def register_agent(self, name: str, crew_config: Mapping[str, Any]) -> AgenticData: + """Register a named crew config for ``run_agent`` lookup.""" + self._agent_registry[name] = dict(crew_config) + return self + + def unregister_agent(self, name: str) -> AgenticData: + """Remove a named crew config if it exists.""" + self._agent_registry.pop(name, None) + return self + + def use_runtime(self, runtime: str, *, strict: bool = True) -> AgenticData: + """Select the active runtime for future agent calls.""" + normalized = runtime.strip().lower() + if normalized == "auto": + normalized = self.select_runtime() + if strict and not self.is_runtime_available(normalized): + raise RuntimeUnavailableError(normalized, install_command(normalized)) + self._active_runtime = normalized + return self + + def clear_runtime(self) -> AgenticData: + """Clear the active runtime selection.""" + self._active_runtime = None + return self + + def runtimes(self) -> list[dict[str, Any]]: + """Return runtime registry metadata with availability.""" + info = runtime_info() + return list(info) if isinstance(info, list) else [info] + + def runtime_info(self, runtime: str | None = None) -> list[dict[str, Any]] | dict[str, Any]: + """Return runtime metadata with current availability.""" + return runtime_info(runtime) + + def is_runtime_available(self, runtime: str) -> bool: + """Return whether a runtime is importable in the current environment.""" + from agentic_fabric.core.decomposer import is_framework_available + + return is_framework_available(runtime) + + def select_runtime( + self, + runtime: str | None = None, + *, + crew_config: Mapping[str, Any] | None = None, + ) -> str: + """Select a runtime using explicit, active, manifest, then auto priority.""" + required_runtime = _manifest_runtime(crew_config) + requested = _normalize_runtime(runtime) + active = _normalize_runtime(self._active_runtime) + + if requested is not None and required_runtime is not None and requested != required_runtime: + msg = f"Crew requires {required_runtime} but {requested} was requested" + raise ValueError(msg) + + if requested is not None: + return _require_available(requested) + + if active is not None: + if required_runtime is not None and active != required_runtime: + msg = f"Active runtime {active} conflicts with crew requirement {required_runtime}" + raise ValueError(msg) + return _require_available(active) + + if required_runtime is not None: + return _require_available(required_runtime) + + from agentic_fabric.core.decomposer import detect_framework + + return detect_framework() + + def run_crew( + self, + crew_config: Mapping[str, Any], + inputs: Mapping[str, Any] | None = None, + *, + runtime: str | None = None, + ) -> str: + """Run one crew config through the selected runtime.""" + from agentic_fabric.core.decomposer import run_crew_auto + + selected_runtime = self.select_runtime(runtime, crew_config=crew_config) + return run_crew_auto(dict(crew_config), inputs=dict(inputs or {}), framework=selected_runtime) + + def run_agent( + self, + agent: str | Mapping[str, Any], + inputs: Mapping[str, Any] | None = None, + *, + runtime: str | None = None, + **input_kwargs: Any, + ) -> str: + """Run a registered agent/crew by name or a direct crew config.""" + crew_config = self._lookup_agent(agent) + merged_inputs = dict(inputs or {}) + merged_inputs.update(input_kwargs) + return self.run_crew(crew_config, merged_inputs, runtime=runtime) + + def call_runtime(self, capability: str, *args: Any, runtime: str | None = None, **kwargs: Any) -> Any: + """Call a declared capability on a selected runtime runner.""" + from agentic_fabric.core.decomposer import get_runner + + selected_runtime = self.select_runtime(runtime) + runner = get_runner(selected_runtime) + return runner.call_capability(capability, *args, **kwargs) + + def __getattr__(self, name: str) -> Any: + """Expose ``run_`` helpers while preserving VendorData dispatch.""" + if name.startswith("run_"): + agent_name = name.removeprefix("run_") + if agent_name in self._agent_registry: + return lambda *args, **kwargs: self.run_agent(agent_name, *args, **kwargs) + + try: + return super().__getattr__(name) # type: ignore[misc] + except AttributeError: + raise AttributeError(f"{type(self).__name__!s} has no attribute {name!r}") from None + + def __dir__(self) -> list[str]: + """Include dynamic registered-agent helpers in introspection.""" + dynamic = [f"run_{name}" for name in self._agent_registry] + return sorted({*super().__dir__(), *dynamic}) + + def _lookup_agent(self, agent: str | Mapping[str, Any]) -> dict[str, Any]: + """Return a crew config from a name or direct mapping.""" + if isinstance(agent, Mapping): + return dict(agent) + try: + return dict(self._agent_registry[agent]) + except KeyError as exc: + options = ", ".join(sorted(self._agent_registry)) or "none registered" + msg = f"Unknown agent '{agent}'. Registered agents: {options}" + raise KeyError(msg) from exc + + +def _normalize_runtime(runtime: str | None) -> str | None: + """Normalize runtime names and treat auto as no explicit choice.""" + if runtime is None: + return None + normalized = runtime.strip().lower() + return None if normalized == "auto" else normalized + + +def _manifest_runtime(crew_config: Mapping[str, Any] | None) -> str | None: + """Read runtime requirements from a crew manifest/config mapping.""" + if crew_config is None: + return None + runtime = crew_config.get("required_framework") or crew_config.get("runtime") or crew_config.get("framework") + return _normalize_runtime(str(runtime)) if runtime else None + + +def _require_available(runtime: str) -> str: + """Return a runtime or raise install guidance.""" + from agentic_fabric.core.decomposer import is_framework_available + + if not is_framework_available(runtime): + raise RuntimeUnavailableError(runtime, install_command(runtime)) + return runtime diff --git a/packages/agentic-fabric/src/agentic_fabric/base/__init__.py b/packages/agentic-fabric/src/agentic_fabric/base/__init__.py new file mode 100644 index 0000000..af28da8 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/base/__init__.py @@ -0,0 +1,16 @@ +"""Base module - reusable agent archetypes and shared tools.""" + +from __future__ import annotations + +from agentic_fabric.tools.file_tools import ( + DirectoryListTool, + GameCodeReaderTool, + GameCodeWriterTool, +) + + +__all__ = [ + "DirectoryListTool", + "GameCodeReaderTool", + "GameCodeWriterTool", +] diff --git a/packages/agentic-fabric/src/agentic_fabric/base/archetypes.yaml b/packages/agentic-fabric/src/agentic_fabric/base/archetypes.yaml new file mode 100644 index 0000000..ac49b7e --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/base/archetypes.yaml @@ -0,0 +1,75 @@ +# Reusable Agent Archetypes +# +# These are base templates that packages can extend in their own agents.yaml. +# Use the 'extends' field to inherit from an archetype. +# +# Example usage in packages/mypackage/.crewai/crews/builder/agents.yaml: +# my_engineer: +# extends: senior_engineer +# variables: +# language: Python +# backstory: | +# {base} +# Additional context specific to my package... + +archetypes: + senior_engineer: + role: "Senior {language} Engineer" + goal: > + Write production-quality {language} code following best practices + and existing patterns in the codebase. + backstory: > + You are a senior developer with 10+ years of experience. + You always read existing code before writing new code. + You follow project conventions exactly. + You write clean, well-documented code with proper types. + + qa_engineer: + role: "Quality Assurance Engineer" + goal: > + Review code for errors, security issues, type safety problems, + and deviations from project conventions. + backstory: > + You specialize in code review with an eye for: + - Type safety and proper interface usage + - Missing imports that would cause errors + - Inconsistent naming conventions + - Performance anti-patterns + - Security vulnerabilities + + technical_lead: + role: "Technical Lead" + goal: > + Ensure all code is complete, correct, and properly integrated + with the existing architecture. + backstory: > + You are the technical lead with final approval authority. + You verify: + - Code fulfills the original requirement + - Architecture patterns are followed + - Code integrates with existing systems + - No regressions or breaking changes + + architect: + role: "Software Architect" + goal: > + Design scalable, maintainable system architectures and + ensure code follows established patterns. + backstory: > + You are a software architect focused on: + - Clean architecture principles + - SOLID design patterns + - Separation of concerns + - Extensibility and maintainability + + designer: + role: "System Designer" + goal: > + Create detailed designs and specifications for new features + that align with project goals. + backstory: > + You are a system designer who: + - Translates requirements into technical designs + - Considers edge cases and error handling + - Documents design decisions + - Balances complexity with maintainability diff --git a/packages/agentic-fabric/src/agentic_fabric/capabilities.py b/packages/agentic-fabric/src/agentic_fabric/capabilities.py new file mode 100644 index 0000000..00682f0 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/capabilities.py @@ -0,0 +1,123 @@ +"""Capability metadata for agent runners and tools.""" + +from __future__ import annotations + +from collections.abc import Callable, Mapping +from dataclasses import dataclass +from types import MappingProxyType +from typing import Any, ClassVar, TypeVar, cast + + +F = TypeVar("F", bound=Callable[..., Any]) + + +@dataclass(frozen=True) +class AgentCapabilitySpec: + """A declared agent/runtime/tool capability.""" + + name: str + kind: str = "runtime" + aliases: tuple[str, ...] = () + description: str = "" + + def as_dict(self) -> dict[str, Any]: + """Return serializable capability metadata.""" + return { + "name": self.name, + "kind": self.kind, + "aliases": list(self.aliases), + "description": self.description, + } + + +def agent_capability( + name: str, + *, + kind: str = "runtime", + aliases: tuple[str, ...] = (), + description: str = "", +) -> Callable[[F], F]: + """Declare an agent-facing capability on a method.""" + + def decorate(method: F) -> F: + specs = list(getattr(method, "_agentic_capabilities", ())) + specs.append( + AgentCapabilitySpec( + name=name, + kind=kind, + aliases=aliases, + description=description, + ) + ) + cast("Any", method)._agentic_capabilities = tuple(specs) + return method + + return decorate + + +def runtime_capability( + name: str, + *, + aliases: tuple[str, ...] = (), + description: str = "", +) -> Callable[[F], F]: + """Declare a runner/runtime capability.""" + return agent_capability(name, kind="runtime", aliases=aliases, description=description) + + +def tool_capability( + name: str, + *, + aliases: tuple[str, ...] = (), + description: str = "", +) -> Callable[[F], F]: + """Declare a tool capability.""" + return agent_capability(name, kind="tool", aliases=aliases, description=description) + + +class AgentCapabilityProviderMixin: + """Collect decorated capability declarations through inheritance.""" + + agent_capabilities: ClassVar[Mapping[str, AgentCapabilitySpec]] + agent_capability_methods: ClassVar[Mapping[str, str]] + + def __init_subclass__(cls, **kwargs: Any) -> None: + """Collect decorated methods across the full MRO.""" + super().__init_subclass__(**kwargs) + capabilities: dict[str, AgentCapabilitySpec] = {} + methods: dict[str, str] = {} + + for owner in reversed(cls.__mro__): + for method_name, member in getattr(owner, "__dict__", {}).items(): + specs = cast("tuple[AgentCapabilitySpec, ...]", getattr(member, "_agentic_capabilities", ())) + for spec in specs: + for public_name in (spec.name, *spec.aliases): + capabilities[public_name] = spec + methods[public_name] = method_name + + cls.agent_capabilities = MappingProxyType(capabilities) + cls.agent_capability_methods = MappingProxyType(methods) + + @classmethod + def list_capabilities(cls, *, kind: str | None = None) -> tuple[AgentCapabilitySpec, ...]: + """Return declared capabilities, optionally filtered by kind.""" + capabilities = list(cls.agent_capabilities.values()) + if kind is not None: + capabilities = [capability for capability in capabilities if capability.kind == kind] + return tuple(capabilities) + + def call_capability(self, name: str, *args: Any, **kwargs: Any) -> Any: + """Dispatch one declared capability by public name.""" + method_name = self.agent_capability_methods.get(name) + if method_name is None: + msg = f"Unknown agent capability: {name}" + raise AttributeError(msg) + method = getattr(self, method_name) + if not callable(method): + msg = f"Agent capability {name!r} is not callable" + raise TypeError(msg) + return method(*args, **kwargs) + + def capability_map(self) -> Mapping[str, AgentCapabilitySpec]: + """Return read-only capability metadata for this instance.""" + return self.agent_capabilities diff --git a/packages/agentic-fabric/src/agentic_fabric/config/__init__.py b/packages/agentic-fabric/src/agentic_fabric/config/__init__.py new file mode 100644 index 0000000..dcef26f --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/config/__init__.py @@ -0,0 +1,10 @@ +"""Configuration for CrewAI agents.""" + +from __future__ import annotations + +from agentic_fabric.config.llm import get_llm + + +__all__ = [ + "get_llm", +] diff --git a/packages/agentic-fabric/src/agentic_fabric/config/agents.yaml b/packages/agentic-fabric/src/agentic_fabric/config/agents.yaml new file mode 100644 index 0000000..a8d8b6a --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/config/agents.yaml @@ -0,0 +1,101 @@ + +technical_director: + role: "Technical Director & Workflow Coordinator" + goal: "Orchestrate Alpha→Work→Omega workflow, delegate to specialists, validate deliverables" + backstory: | + You are an experienced technical director who understands how to decompose complex + game development into vertical slices. You validate each slice before proceeding, + ensure proper handoffs between specialists, and maintain architectural coherence. + You understand when to parallelize work and when sequential validation is critical. + llm: "claude-3-7-sonnet-20250219" + max_iter: 40 + max_reasoning_attempts: 5 + allow_delegation: true + tools: + - "mcp://git/execute_command" + - "mcp://filesystem/read_file" + - "mcp://filesystem/write_file" + - "mcp://conport/get_conport_schema" + +project_manager: + role: "Project Manager (Alpha Agent - Context Reader)" + goal: "Analyze projectbrief.md and ConPort, identify blockers, validate prerequisites" + backstory: | + You start every workflow by reading complete project context. You identify missing + information, blockers, dependencies, and required resources. You create actionable + checklists and validation criteria for downstream tasks. + llm: "claude-3-5-sonnet-20241022" + max_iter: 25 + tools: + - "mcp://conport/get_conport_schema" + - "mcp://filesystem/read_file" + - "mcp://git/execute_command" + +ecs_architect: + role: "ECS & Data Architecture Lead" + goal: "Design type-safe ECS schemas, validate data contracts, ensure zero TypeScript errors" + backstory: | + You specialize in Entity Component System design with Miniplex and TypeScript. + You create minimal, focused schemas that solve ONE problem well. You validate + against existing patterns and ensure backward compatibility. + llm: "claude-3-5-sonnet-20241022" + max_iter: 30 + max_reasoning_attempts: 3 + tools: + - "mcp://filesystem/read_file" + - "mcp://filesystem/write_file" + - "mcp://git/execute_command" + +rendering_engineer: + role: "3D Graphics & Rendering Engineer" + goal: "Build performant React Three Fiber scenes, optimize shaders, maintain 60fps" + backstory: | + You create stunning R3F/Three.js visuals with GLSL shaders while maintaining + 60fps on mobile. You understand the post-processing pipeline requirements and + mobile performance constraints. + llm: "claude-3-5-sonnet-20241022" + max_iter: 30 + max_reasoning_attempts: 3 + tools: + - "mcp://filesystem/read_file" + - "mcp://filesystem/write_file" + +integration_specialist: + role: "Frontend Integration Specialist" + goal: "Connect backend deliverables to R3F frontend, validate end-to-end flow" + backstory: | + You specialize in integrating backend systems with React Three Fiber frontends. + You create adapters, validate type contracts, and ensure smooth data flow from + ECS to rendering pipeline. + llm: "claude-3-5-sonnet-20241022" + max_iter: 30 + tools: + - "mcp://filesystem/read_file" + - "mcp://filesystem/write_file" + +qa_validator: + role: "Quality Assurance & Validation Specialist" + goal: "Validate deliverables against acceptance criteria, run tests, verify performance" + backstory: | + You validate that deliverables meet all acceptance criteria. You run unit tests, + check TypeScript compilation, measure performance, and create validation reports. + You identify gaps before human-in-the-loop review. + llm: "claude-3-5-sonnet-20241022" + max_iter: 25 + tools: + - "mcp://filesystem/read_file" + - "mcp://git/execute_command" + - "mcp://playwright/execute" + +technical_writer: + role: "Technical Writer (Omega Agent - Context Writer)" + goal: "Document decisions, update ConPort, create integration guides" + backstory: | + You end every workflow by updating ConPort with decisions and progress. + You create clear, actionable documentation that the next agent or human + can use to continue work. + llm: "claude-3-5-sonnet-20241022" + max_iter: 25 + tools: + - "mcp://conport/get_conport_schema" + - "mcp://filesystem/write_file" diff --git a/packages/agentic-fabric/src/agentic_fabric/config/llm.py b/packages/agentic-fabric/src/agentic_fabric/config/llm.py new file mode 100644 index 0000000..80c049c --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/config/llm.py @@ -0,0 +1,222 @@ +"""LLM Configuration for CrewAI agents. + +Uses Anthropic Claude directly via ANTHROPIC_API_KEY. +Falls back to OpenRouter if OPENROUTER_API_KEY is set. +""" + +from __future__ import annotations + +import os + +from dataclasses import dataclass +from enum import Enum + + +try: + from crewai import LLM +except ImportError: + # Allow module to load even if crewai not installed (for testing) + LLM = None # type: ignore[assignment] + + +class LLMProvider(Enum): + """Available LLM providers.""" + + ANTHROPIC = "anthropic" + OPENROUTER = "openrouter" + + +@dataclass(frozen=True) +class LLMConfig: + """Configuration for specific LLM use cases.""" + + model: str + temperature: float + description: str + + +# Model identifiers +_CLAUDE_HAIKU_45 = "claude-haiku-4-5-20251001" +_CLAUDE_SONNET_45 = "claude-sonnet-4-5-20250929" +_CLAUDE_SONNET_4 = "claude-sonnet-4-20250514" +_CLAUDE_OPUS_4 = "claude-opus-4-20250514" + +# Default model - Claude Haiku 4.5 for fast, cost-effective operations +DEFAULT_MODEL = _CLAUDE_HAIKU_45 + +# Alternative models +MODELS = { + "haiku": _CLAUDE_HAIKU_45, + "sonnet": _CLAUDE_SONNET_45, + "sonnet-4": _CLAUDE_SONNET_4, + "opus": _CLAUDE_OPUS_4, + # OpenRouter fallbacks + "openrouter-auto": "openrouter/auto", + "openrouter-haiku": "openrouter/anthropic/claude-haiku-4.5", +} + +# Predefined configurations for common use cases +LLM_CONFIGS = { + "reasoning": LLMConfig(model=_CLAUDE_OPUS_4, temperature=0.3, description="Optimized for complex reasoning tasks"), + "creative": LLMConfig( + model=_CLAUDE_SONNET_45, + temperature=0.8, + description="Optimized for creative content generation", + ), + "code": LLMConfig( + model=_CLAUDE_SONNET_45, + temperature=0.2, + description="Optimized for code generation and analysis", + ), + "default": LLMConfig( + model=_CLAUDE_HAIKU_45, + temperature=0.7, + description="Balanced configuration for general use", + ), +} + + +def get_llm(model: str = DEFAULT_MODEL, temperature: float = 0.7, provider: LLMProvider | None = None) -> LLM | None: + """Get configured LLM instance for CrewAI agents. + + Args: + model: Model identifier. Defaults to claude-haiku-4-5-20251001 (DEFAULT_MODEL) + temperature: Sampling temperature (0.0-1.0). Lower = more focused, + higher = more creative. + provider: Force specific provider (ANTHROPIC or OPENROUTER). + If None, auto-detects based on available API keys. + + Available models: + - claude-haiku-4-5-20251001 (default - fast, cost-effective) + - claude-sonnet-4-5-20250929 (best for code and creative) + - claude-sonnet-4-20250514 (capable general purpose) + - claude-opus-4-20250514 (most capable) + - openrouter/auto (fallback via OpenRouter) + + Returns: + Configured LLM instance, or None if no API key set + + Note: + Tries ANTHROPIC_API_KEY first, falls back to OPENROUTER_API_KEY. + Returns None if neither is set, allowing CrewAI to use its default. + + Example: + >>> llm = get_llm() # Uses Claude Haiku 4.5 + >>> llm = get_llm("claude-opus-4-20250514", temperature=0.3) + >>> llm = get_llm(provider=LLMProvider.OPENROUTER) + """ + if LLM is None: + return None + + # Determine provider + anthropic_key = os.getenv("ANTHROPIC_API_KEY") + openrouter_key = os.getenv("OPENROUTER_API_KEY") + + # Force provider if specified + if provider == LLMProvider.ANTHROPIC: + if not anthropic_key: + return None + return _create_anthropic_llm(model, temperature, anthropic_key) + + if provider == LLMProvider.OPENROUTER: + if not openrouter_key: + return None + return _create_openrouter_llm(model, temperature, openrouter_key) + + # Auto-detect: Try Anthropic first for direct Claude models + if anthropic_key and not model.startswith("openrouter/"): + return _create_anthropic_llm(model, temperature, anthropic_key) + + # Fall back to OpenRouter + if openrouter_key: + return _create_openrouter_llm(model, temperature, openrouter_key) + + return None + + +def _create_anthropic_llm(model: str, temperature: float, api_key: str) -> LLM: + """Create Anthropic LLM instance.""" + return LLM( + model=model, + api_key=api_key, + temperature=temperature, + ) + + +def _create_openrouter_llm(model: str, temperature: float, api_key: str) -> LLM: + """Create OpenRouter LLM instance.""" + # Convert model name to OpenRouter format if needed + if not model.startswith("openrouter/"): + model = MODELS.get("openrouter-auto", "openrouter/auto") + + return LLM( + model=model, + api_key=api_key, + base_url="https://openrouter.ai/api/v1", + temperature=temperature, + ) + + +def get_llm_or_raise(model: str = DEFAULT_MODEL, temperature: float = 0.7, provider: LLMProvider | None = None) -> LLM: + """Get configured LLM instance, raising if API key not set. + + Use this when you need to ensure an LLM is available. + + Args: + model: Model identifier (see get_llm for options) + temperature: Sampling temperature (0.0-1.0) + provider: Force specific provider (optional) + + Returns: + Configured LLM instance + + Raises: + ValueError: If neither ANTHROPIC_API_KEY nor OPENROUTER_API_KEY is set + """ + llm = get_llm(model, temperature, provider) + if llm is None: + raise ValueError( + "ANTHROPIC_API_KEY or OPENROUTER_API_KEY environment variable must be set. " + "Get Anthropic key at https://console.anthropic.com/ or " + "OpenRouter key at https://openrouter.ai/" + ) + return llm + + +def get_llm_for_task(task: str) -> LLM | None: + """Get LLM configured for a specific task type. + + Args: + task: Task type - one of: 'reasoning', 'creative', 'code', 'default' + + Returns: + Configured LLM instance, or None if no API key set + + Raises: + ValueError: If task type is unknown + + Example: + >>> llm = get_llm_for_task('code') # Low temp, optimized for code + >>> llm = get_llm_for_task('creative') # High temp, creative output + """ + if task not in LLM_CONFIGS: + raise ValueError(f"Unknown task type: {task}. Available: {', '.join(LLM_CONFIGS.keys())}") + + config = LLM_CONFIGS[task] + return get_llm(config.model, config.temperature) + + +# Convenience functions for specific use cases (backward compatibility) +def get_reasoning_llm() -> LLM | None: + """Get LLM optimized for complex reasoning tasks.""" + return get_llm_for_task("reasoning") + + +def get_creative_llm() -> LLM | None: + """Get LLM optimized for creative tasks.""" + return get_llm_for_task("creative") + + +def get_code_llm() -> LLM | None: + """Get LLM optimized for code generation.""" + return get_llm_for_task("code") diff --git a/packages/agentic-fabric/src/agentic_fabric/config/tasks.yaml b/packages/agentic-fabric/src/agentic_fabric/config/tasks.yaml new file mode 100644 index 0000000..41b8d16 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/config/tasks.yaml @@ -0,0 +1,177 @@ + +# ------------------------------------------------------------------------- +# PHASE 0: ALPHA - Context Loading & Validation +# ------------------------------------------------------------------------- +alpha_context_load: + description: | + ALPHA TASK: Load complete project context before any work begins. + + Actions: + 1. Read projectbrief.md for project goals and constraints + 2. Query ConPort schema to understand current state + 3. Read .ruler/ files for architectural patterns + 4. Identify any missing prerequisites or blockers + + Validation Criteria: + - All context files are accessible + - No critical blockers identified + - Prerequisites are documented + + Output Format: Markdown checklist with: + - [ ] Context item loaded + - [ ] Prerequisite validated + - [ ] Blocker identified (if any) + expected_output: | + Markdown report with: + ## Context Loaded + - List of files read + - Current project state summary + + ## Prerequisites + - [ ] Checklist of validated prerequisites + + ## Blockers (if any) + - Description of any blockers + + ## Ready to Proceed + - Yes/No with reasoning + agent: project_manager + human_input: false + +# ------------------------------------------------------------------------- +# PHASE 1: FOUNDATION - Parallel schema validation +# ------------------------------------------------------------------------- +validate_ecs_component_schemas: + description: | + Validate existing ECS component schemas in src/ecs/. + + Focus Areas: + 1. TypeScript compilation with zero errors + 2. Adherence to Miniplex patterns + 3. Type exports are complete and correct + 4. Component schemas are minimal and focused + + DO NOT create new components. ONLY validate existing ones. + + Validation Steps: + 1. Run `pnpm run build` to check TypeScript compilation + 2. Check each component file against patterns + 3. Verify type exports exist + 4. Document any issues found + + If issues found: Create a remediation plan, do NOT fix yet. + expected_output: | + ## Validation Report + + ### TypeScript Compilation + - Status: PASS/FAIL + - Errors: (list if any) + + ### Pattern Compliance + - AIComponent.ts: PASS/FAIL (reasoning) + - AnimationComponent.ts: PASS/FAIL (reasoning) + - [continue for all components] + + ### Type Exports + - Complete: YES/NO + - Missing: (list if any) + + ### Remediation Plan (if needed) + - Issue 1: [description] → [fix] + - Issue 2: [description] → [fix] + + ### Ready for Next Phase + - YES/NO with reasoning + agent: ecs_architect + context: [alpha_context_load] + async_execution: false + +validate_dfu_data_contracts: + description: | + Validate Daggerfall Unity data mappings in shared/backend/dfu_analysis/. + + Focus Areas: + 1. biomes.json structure matches BiomeContract.ts + 2. species.json structure matches SpeciesContract.ts + 3. Data completeness (no missing required fields) + 4. Type safety between backend and frontend contracts + + DO NOT modify contracts yet. ONLY validate. + + Validation Steps: + 1. Load shared/contracts/*.ts + 2. Load shared/backend/dfu_analysis/mappings/*.json + 3. Verify structural alignment + 4. Check for missing data + 5. Document any mismatches + expected_output: | + ## Contract Validation Report + + ### BiomeContract Alignment + - Structure matches: YES/NO + - Missing fields: (list if any) + - Type mismatches: (list if any) + + ### SpeciesContract Alignment + - Structure matches: YES/NO + - Missing fields: (list if any) + - Type mismatches: (list if any) + + ### Data Completeness + - Biomes: X/Y complete + - Species: X/Y complete + + ### Remediation Plan (if needed) + - Issue 1: [description] → [fix] + + ### Ready for Next Phase + - YES/NO with reasoning + agent: ecs_architect + context: [alpha_context_load] + async_execution: false + +# ------------------------------------------------------------------------- +# OMEGA: Documentation & Context Recording +# ------------------------------------------------------------------------- +omega_record_progress: + description: | + OMEGA TASK: Record all decisions, progress, and learnings into ConPort. + + Actions: + 1. Summarize all completed work + 2. Document architectural decisions made + 3. Record patterns discovered + 4. Update ConPort with current state + 5. Create handoff notes for next phase + + This runs AFTER every major phase completion. + expected_output: | + ## ConPort Update + + ### Work Completed + - Phase 1: [summary] + - Phase 2: [summary] + - Phase 3: [summary] + + ### Architectural Decisions + - Decision 1: [what, why, alternatives considered] + - Decision 2: [what, why, alternatives considered] + + ### Patterns Discovered + - Pattern 1: [description, when to use] + - Pattern 2: [description, when to use] + + ### Current State + - ECS schemas: [status] + - Prototypes: [list with status] + - Technical debt: [list] + + ### Handoff Notes for Next Phase + - Prerequisites: [checklist] + - Context to review: [files] + - Open questions: [list] + + ### ConPort Updated + - YES/NO with timestamp + agent: technical_writer + context: [validate_ecs_component_schemas, validate_dfu_data_contracts] diff --git a/packages/agentic-fabric/src/agentic_fabric/core/__init__.py b/packages/agentic-fabric/src/agentic_fabric/core/__init__.py new file mode 100644 index 0000000..7449738 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/core/__init__.py @@ -0,0 +1,18 @@ +"""Core CrewAI engine - discovery, loading, and running of package-defined crews.""" + +from __future__ import annotations + +from agentic_fabric.core.discovery import discover_packages, get_crew_config, load_manifest +from agentic_fabric.core.loader import load_crew_from_config +from agentic_fabric.core.manager import ManagerAgent +from agentic_fabric.core.runner import run_crew + + +__all__ = [ + "ManagerAgent", + "discover_packages", + "get_crew_config", + "load_crew_from_config", + "load_manifest", + "run_crew", +] diff --git a/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py b/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py new file mode 100644 index 0000000..c0a565c --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py @@ -0,0 +1,299 @@ +"""Framework decomposition - auto-detect and select AI framework. + +This module provides the core capability of agentic-fabric: declaring crews +once and running them on CrewAI, LangGraph, or Strands depending on what's +installed. It also supports single-agent CLI runners for simpler tasks. + +Usage: + from agentic_fabric.core.decomposer import get_runner, detect_framework + + # Auto-detect best framework for multi-agent + framework = detect_framework() + + # Get runner for that framework + runner = get_runner(framework) + + # Or let it auto-select + runner = get_runner() # Uses best available + + # Get single-agent CLI runner + from agentic_fabric.core.decomposer import get_cli_runner + runner = get_cli_runner("aider") +""" + +from __future__ import annotations + +import importlib + +from typing import TYPE_CHECKING, Any + +from agentic_fabric.runners.registry import get_runtime_spec, runtime_info, runtime_names + + +if TYPE_CHECKING: + from agentic_fabric.runners.base import BaseRunner + from agentic_fabric.runners.single_agent_runner import SingleAgentRunner + +# Framework detection cache +_framework_cache: dict[str, bool] = {} + +# Framework priority (first available wins) +FRAMEWORK_PRIORITY = runtime_names() + + +def is_framework_available(framework: str) -> bool: + """Check if a framework is installed and importable. + + Args: + framework: Framework name (crewai, langgraph, strands) + + Returns: + True if framework is available + """ + if framework in _framework_cache: + return _framework_cache[framework] + + if framework not in FRAMEWORK_PRIORITY: + _framework_cache[framework] = False + return False + + try: + spec = get_runtime_spec(framework) + importlib.import_module(spec.import_name) + _framework_cache[framework] = True + return True + except ImportError: + _framework_cache[framework] = False + return False + + +def detect_framework(preferred: str | None = None) -> str: + """Detect the best available AI framework. + + Args: + preferred: Optional preferred framework. If available, use it. + + Returns: + Name of the best available framework. + + Raises: + RuntimeError: If no frameworks are installed. + """ + # Check preferred first + if preferred and preferred != "auto" and is_framework_available(preferred): + return preferred + # Fall through to auto-detect if preferred not available + + # Auto-detect based on priority + for framework in FRAMEWORK_PRIORITY: + if is_framework_available(framework): + return framework + + raise RuntimeError( + "No AI frameworks installed. Install one of:\n" + " pip install crewai[tools]\n" + " pip install langgraph\n" + " pip install strands-agents" + ) + + +def get_available_frameworks() -> list[str]: + """Get list of all available frameworks. + + Returns: + List of framework names that are installed. + """ + return [f for f in FRAMEWORK_PRIORITY if is_framework_available(f)] + + +def get_runner(framework: str | None = None) -> BaseRunner: + """Get a runner for the specified or auto-detected framework. + + Args: + framework: Framework name or None for auto-detect. + + Returns: + Runner instance for the framework. + + Raises: + RuntimeError: If framework not available. + ValueError: If unknown framework specified. + """ + if framework is None or framework == "auto": + framework = detect_framework() + + try: + spec = get_runtime_spec(framework) + except ValueError as exc: + raise ValueError(f"Unknown framework: {framework}. Options: {FRAMEWORK_PRIORITY}") from exc + + module = importlib.import_module(spec.runner_module) + runner_cls = getattr(module, spec.runner_class) + return runner_cls() + + +def get_cli_runner( + profile: str | dict[str, Any], + model: str | None = None, +) -> SingleAgentRunner: + """Get a single-agent CLI runner for the specified profile. + + Args: + profile: Profile name (e.g., "aider", "claude-code", "ollama") or + custom config dict. + model: Optional model override. + + Returns: + LocalCLIRunner instance for the profile. + + Raises: + ValueError: If profile not found. + FileNotFoundError: If profiles file missing. + + Examples: + # Use built-in profile + runner = get_cli_runner("aider") + result = runner.run("Add error handling to auth.py") + + # Use with model override + runner = get_cli_runner("ollama", model="deepseek-coder") + result = runner.run("Fix the bug") + + # Use custom config + runner = get_cli_runner({ + "command": "my-tool", + "task_flag": "--task", + "auto_approve": "--yes", + }) + """ + from agentic_fabric.runners.local_cli_runner import LocalCLIRunner + + return LocalCLIRunner(profile, model=model) + + +def get_available_cli_runners() -> list[str]: + """Get list of available CLI runner profiles. + + Returns: + List of profile names (e.g., ["aider", "claude-code", "ollama"]). + """ + from agentic_fabric.runners.local_cli_runner import LocalCLIRunner + + return LocalCLIRunner.get_available_profiles() + + +def get_framework_info(framework: str | None = None) -> list[dict[str, Any]] | dict[str, Any]: + """Return lazy runtime registry metadata with current availability.""" + return runtime_info(framework) + + +def is_cli_runner_available(profile: str) -> bool: + """Check if a CLI runner profile is available (tool installed). + + Args: + profile: Profile name to check. + + Returns: + True if the tool is installed and accessible. + """ + try: + runner = get_cli_runner(profile) + return runner.is_available() + except (ValueError, FileNotFoundError): + return False + + +def decompose_crew( + crew_config: dict[str, Any], + framework: str | None = None, +) -> Any: + """Decompose a crew configuration to a framework-specific crew. + + This is the core function that converts a framework-agnostic crew + definition into a runnable crew for the target framework. + + Args: + crew_config: Crew configuration from loader. + framework: Target framework or None for auto-detect. + If crew_config has required_framework, that takes precedence. + + Returns: + Framework-specific crew object ready to run. + + Raises: + RuntimeError: If required framework is not available. + """ + # Check if crew config requires a specific framework + required_framework = crew_config.get("required_framework") + + if required_framework: + # Framework is enforced by config directory (.crewai, .strands, etc.) + if framework and framework not in (required_framework, "auto"): + raise ValueError( + f"Crew requires {required_framework} (defined in .{required_framework}/ directory) " + f"but {framework} was requested" + ) + + if not is_framework_available(required_framework): + raise RuntimeError( + f"Crew requires {required_framework} but it's not installed. " + f"Install with: pip install {_get_install_command(required_framework)}" + ) + framework = required_framework + + runner = get_runner(framework) + return runner.build_crew(crew_config) + + +def _get_install_command(framework: str) -> str: + """Get the pip install command for a framework.""" + install_commands = { + "crewai": "crewai[tools]", + "langgraph": "langgraph langchain-anthropic", + "strands": "strands-agents", + } + return install_commands.get(framework, framework) + + +# Convenience function for simple use cases +def run_crew_auto( + crew_config: dict[str, Any], + inputs: dict[str, Any] | None = None, + framework: str | None = None, +) -> str: + """Run a crew using the best available framework. + + Args: + crew_config: Crew configuration from loader. + inputs: Optional inputs for the crew. + framework: Optional framework override. If crew_config has + required_framework (from .crewai/.strands/.langgraph dir), + that takes precedence. + + Returns: + Crew output as string. + + Raises: + RuntimeError: If required framework is not available. + ValueError: If requested framework conflicts with required framework. + """ + # Check if crew config requires a specific framework + required_framework = crew_config.get("required_framework") + + if required_framework: + if framework and framework not in (required_framework, "auto"): + raise ValueError( + f"Crew requires {required_framework} (defined in .{required_framework}/ directory) " + f"but {framework} was requested" + ) + + if not is_framework_available(required_framework): + raise RuntimeError( + f"Crew requires {required_framework} but it's not installed. " + f"Install with: pip install {_get_install_command(required_framework)}" + ) + framework = required_framework + + runner = get_runner(framework) + crew = runner.build_crew(crew_config) + return runner.run(crew, inputs or {}) diff --git a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py new file mode 100644 index 0000000..bc89496 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py @@ -0,0 +1,307 @@ +"""Discovery module - finds packages with crew configuration directories. + +Supports framework-specific configuration directories: +- .crewai/ - CrewAI-specific configurations (default) +- .langgraph/ - LangGraph-specific configurations +- .strands/ - Strands-specific configurations + +The discovery order matches framework priority for auto-detection. +""" + +from __future__ import annotations + +import logging + +from pathlib import Path +from typing import Any + +import yaml + + +logger = logging.getLogger(__name__) + +# Framework directory names in priority order +# .crew is framework-agnostic (can run on any available framework) +# Framework-specific dirs enforce that framework +FRAMEWORK_DIRS = [".crew", ".crewai", ".langgraph", ".strands"] + +# Mapping from directory name to framework name +# .crew maps to None (framework-agnostic, auto-detect at runtime) +DIR_TO_FRAMEWORK: dict[str, str | None] = { + ".crew": None, # Framework-agnostic + ".crewai": "crewai", + ".langgraph": "langgraph", + ".strands": "strands", +} + +# Mapping from framework name to directory name +# None (agnostic) maps to .crew +FRAMEWORK_TO_DIR: dict[str | None, str] = { + None: ".crew", + "crewai": ".crewai", + "langgraph": ".langgraph", + "strands": ".strands", +} + + +def get_workspace_root() -> Path: + """Get the workspace root directory. + + Walks up from the current file to find the root (where pyproject.toml is). + """ + current = Path(__file__).resolve() + for parent in current.parents: + if (parent / "pyproject.toml").exists() and (parent / "packages").exists(): + return parent + # Fallback to current working directory + return Path.cwd() + + +def discover_packages( + workspace_root: Path | None = None, + framework: str | None = None, +) -> dict[str, Path]: + """Discover all packages with crew configuration directories. + + Args: + workspace_root: Root of the workspace. If None, auto-detected. + framework: Optional framework to filter by (crewai, langgraph, strands). + If None, returns first found directory per package. + + Returns: + Dict mapping package name to its config directory path. + """ + if workspace_root is None: + workspace_root = get_workspace_root() + + packages = {} + + # Determine which directories to look for + if framework: + dir_name = FRAMEWORK_TO_DIR.get(framework) + dirs_to_check = [dir_name] if dir_name else FRAMEWORK_DIRS + else: + dirs_to_check = FRAMEWORK_DIRS + + # Check packages/ directory + packages_dir = workspace_root / "packages" + if packages_dir.exists(): + for pkg_dir in packages_dir.iterdir(): + if not pkg_dir.is_dir(): + continue + + # Try each framework directory in priority order + for dir_name in dirs_to_check: + config_dir = pkg_dir / dir_name + if config_dir.exists() and (config_dir / "manifest.yaml").exists(): + packages[pkg_dir.name] = config_dir + break # Use first found (highest priority) + + # Also check workspace root for standalone projects + for dir_name in dirs_to_check: + config_dir = workspace_root / dir_name + if config_dir.exists() and (config_dir / "manifest.yaml").exists(): + # Use the workspace name or a default + pkg_name = workspace_root.name or "default" + if pkg_name not in packages: + packages[pkg_name] = config_dir + break + + return packages + + +def discover_all_framework_configs( + workspace_root: Path | None = None, +) -> dict[str, dict[str | None, Path]]: + """Discover all framework-specific config directories for all packages. + + This finds ALL framework directories, not just the first one per package. + + Args: + workspace_root: Root of the workspace. If None, auto-detected. + + Returns: + Dict mapping package name to dict of framework -> config_dir. + Example: {"otterfall": {"crewai": Path(...), "strands": Path(...)}} + Framework can be None for framework-agnostic .crew/ directories. + """ + if workspace_root is None: + workspace_root = get_workspace_root() + + packages: dict[str, dict[str | None, Path]] = {} + + # Check packages/ directory + packages_dir = workspace_root / "packages" + if packages_dir.exists(): + for pkg_dir in packages_dir.iterdir(): + if not pkg_dir.is_dir(): + continue + + pkg_configs: dict[str | None, Path] = {} + for dir_name in FRAMEWORK_DIRS: + config_dir = pkg_dir / dir_name + if config_dir.exists() and (config_dir / "manifest.yaml").exists(): + framework = DIR_TO_FRAMEWORK[dir_name] + pkg_configs[framework] = config_dir + + if pkg_configs: + packages[pkg_dir.name] = pkg_configs + + # Also check workspace root + root_configs: dict[str | None, Path] = {} + for dir_name in FRAMEWORK_DIRS: + config_dir = workspace_root / dir_name + if config_dir.exists() and (config_dir / "manifest.yaml").exists(): + framework = DIR_TO_FRAMEWORK[dir_name] + root_configs[framework] = config_dir + + if root_configs: + pkg_name = workspace_root.name or "default" + if pkg_name not in packages: + packages[pkg_name] = root_configs + + return packages + + +def load_manifest(crewai_dir: Path) -> dict[str, Any]: + """Load a package's CrewAI manifest. + + Args: + crewai_dir: Path to the .crewai/ directory. + + Returns: + Parsed manifest as a dictionary. + """ + manifest_path = crewai_dir / "manifest.yaml" + with open(manifest_path) as f: + result = yaml.safe_load(f) + return result or {} + + +def get_framework_from_config_dir(config_dir: Path) -> str | None: + """Determine the required framework from a config directory path. + + Args: + config_dir: Path to a framework config directory (.crewai, .strands, etc.) + + Returns: + Framework name if directory indicates a specific framework, None otherwise. + """ + dir_name = config_dir.name + return DIR_TO_FRAMEWORK.get(dir_name) + + +def get_crew_config(config_dir: Path, crew_name: str) -> dict: + """Load a specific crew's configuration. + + Args: + config_dir: Path to the config directory (.crewai/, .strands/, .langgraph/). + crew_name: Name of the crew to load. + + Returns: + Dict with agents, tasks, knowledge_paths, and required_framework. + The required_framework field indicates which framework MUST be used + if the config is in a framework-specific directory. + + Raises: + ValueError: If crew not found in manifest. + """ + manifest = load_manifest(config_dir) + crews = manifest.get("crews", {}) + crew_config = crews.get(crew_name) + + if not crew_config: + available = list(crews.keys()) + raise ValueError(f"Crew '{crew_name}' not found. Available: {available}") + + # Load agents and tasks YAML + agents_path = config_dir / crew_config["agents"] + tasks_path = config_dir / crew_config["tasks"] + + agents = yaml.safe_load(agents_path.read_text()) if agents_path.exists() else {} + tasks = yaml.safe_load(tasks_path.read_text()) if tasks_path.exists() else {} + + # Resolve knowledge paths + knowledge_paths = [] + for kp in crew_config.get("knowledge", []): + full_path = config_dir / kp + if full_path.exists(): + knowledge_paths.append(full_path) + + # Determine required framework from directory name + # If config is in .crewai/, it MUST run on CrewAI, etc. + required_framework = get_framework_from_config_dir(config_dir) + + # Also check manifest-level preferred_framework (can be overridden by dir) + manifest_framework = crew_config.get("preferred_framework") + if ( + manifest_framework + and manifest_framework != "auto" + and required_framework + and manifest_framework != required_framework + ): + # Manifest can specify preference, but directory takes precedence + logger.warning( + f"Warning: Crew '{crew_name}' specifies preferred_framework={manifest_framework} " + f"but is in {config_dir.name}/ directory which requires {required_framework}" + ) + + # Get LLM config from manifest + llm_config = manifest.get("llm", crew_config.get("llm", {})) + + return { + "name": crew_name, + "description": crew_config.get("description", ""), + "agents": agents, + "tasks": tasks, + "knowledge_paths": knowledge_paths, + "manifest": manifest, + "config_dir": config_dir, + # Framework enforcement + "required_framework": required_framework, + "preferred_framework": manifest_framework, + # LLM configuration + "llm": llm_config, + } + + +def list_crews( + package_name: str | None = None, + framework: str | None = None, +) -> dict[str, list[dict]]: + """List all available crews, optionally filtered by package or framework. + + Args: + package_name: If provided, only list crews for this package. + framework: If provided, only list crews that can run on this framework. + + Returns: + Dict mapping package name to list of crew info dicts. + Each crew dict includes: + - name: Crew name + - description: Crew description + - required_framework: Framework required (if in framework-specific dir) + """ + packages = discover_packages(framework=framework) + result = {} + + for pkg_name, config_dir in packages.items(): + if package_name and pkg_name != package_name: + continue + + manifest = load_manifest(config_dir) + required_framework = get_framework_from_config_dir(config_dir) + + crews = [] + for crew_name, crew_config in manifest.get("crews", {}).items(): + crews.append( + { + "name": crew_name, + "description": crew_config.get("description", ""), + "required_framework": required_framework, + "preferred_framework": crew_config.get("preferred_framework"), + } + ) + result[pkg_name] = crews + + return result diff --git a/packages/agentic-fabric/src/agentic_fabric/core/loader.py b/packages/agentic-fabric/src/agentic_fabric/core/loader.py new file mode 100644 index 0000000..02942c9 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/core/loader.py @@ -0,0 +1,195 @@ +"""Loader module - creates CrewAI Crew objects from YAML configurations. + +Note: This module imports CrewAI lazily to allow the core package to be +installed without requiring CrewAI. For framework-agnostic usage, prefer +using the decomposer module with runners instead. +""" + +from __future__ import annotations + +import logging + +from pathlib import Path +from typing import TYPE_CHECKING, Any + + +if TYPE_CHECKING: + from crewai import Agent, Crew, Task + +from agentic_fabric.tools.registry import resolve_tools + + +logger = logging.getLogger(__name__) + + +def load_knowledge_sources(knowledge_paths: list[Path]) -> list: + """Load knowledge sources from the specified paths. + + Args: + knowledge_paths: List of paths to knowledge directories. + + Returns: + List of TextFileKnowledgeSource objects. + + Raises: + ImportError: If crewai is not installed. + """ + from crewai.knowledge.source.text_file_knowledge_source import TextFileKnowledgeSource + + sources = [] + + for knowledge_path in knowledge_paths: + if not knowledge_path.exists(): + continue + + # Load .md and .ts files + for ext in ["*.md", "*.ts", "*.tsx", "*.py"]: + for file_path in knowledge_path.rglob(ext): + try: + # Read file content directly + content = file_path.read_text(encoding="utf-8") + if content.strip(): + sources.append( + TextFileKnowledgeSource( + file_paths=[str(file_path)], + ) + ) + except (OSError, ValueError) as exc: + logger.warning("Could not load knowledge source %s: %s", file_path, exc) + + return sources + + +def create_agent_from_config( + agent_name: str, + agent_config: dict, + tools: list | None = None, +) -> Agent: + """Create an Agent from YAML configuration. + + Args: + agent_name: Name/key of the agent. + agent_config: Agent configuration dict with role, goal, backstory. + tools: Optional list of tools to give the agent. + + Returns: + Configured Agent instance. + + Raises: + ImportError: If crewai is not installed. + """ + from crewai import Agent + + from agentic_fabric.config.llm import get_llm + + return Agent( + role=agent_config.get("role", agent_name), + goal=agent_config.get("goal", ""), + backstory=agent_config.get("backstory", ""), + llm=get_llm(), + tools=tools or [], + allow_delegation=agent_config.get("allow_delegation", False), + verbose=True, + ) + + +def create_task_from_config( + task_name: str, + task_config: dict, + agent: Agent, +) -> Task: + """Create a Task from YAML configuration. + + Args: + task_name: Name/key of the task. + task_config: Task configuration dict with description, expected_output. + agent: Agent to assign to this task. + + Returns: + Configured Task instance. + + Raises: + ImportError: If crewai is not installed. + """ + from crewai import Task + + return Task( + description=task_config.get("description", ""), + expected_output=task_config.get("expected_output", ""), + agent=agent, + ) + + +def load_crew_from_config(crew_config: dict) -> Crew: + """Load a complete Crew from configuration. + + Args: + crew_config: Configuration dict from get_crew_config(). + + Returns: + Configured Crew instance ready to kickoff. + + Raises: + ImportError: If crewai is not installed. + """ + from crewai import Crew, Process + + from agentic_fabric.tools.file_tools import ( + DirectoryListTool, + GameCodeReaderTool, + GameCodeWriterTool, + ) + + # Initialize tools + code_writer = GameCodeWriterTool() + code_reader = GameCodeReaderTool() + dir_lister = DirectoryListTool() + all_tools = [code_writer, code_reader, dir_lister] + read_tools = [code_reader, dir_lister] + + # Create agents + agents_config = crew_config.get("agents", {}) + agents: dict[str, Any] = {} + + for agent_name, agent_cfg in agents_config.items(): + resolved_tools = resolve_tools(agent_cfg.get("tools", [])) + + # Preserve the older role-based fallback when no concrete tool could be resolved. + if resolved_tools: + tools = resolved_tools + else: + tools = all_tools if "engineer" in agent_name.lower() or "developer" in agent_name.lower() else read_tools + + agents[agent_name] = create_agent_from_config(agent_name, agent_cfg, tools) + + # Create tasks + tasks_config = crew_config.get("tasks", {}) + tasks = [] + + for task_name, task_cfg in tasks_config.items(): + # Find the agent for this task + agent_name = task_cfg.get("agent") + if not agent_name: + raise ValueError(f"Task '{task_name}' is missing an 'agent' assignment.") + + agent = agents.get(agent_name) + if not agent: + raise ValueError( + f"Agent '{agent_name}' for task '{task_name}' not found. Available agents: {list(agents.keys())}" + ) + + tasks.append(create_task_from_config(task_name, task_cfg, agent)) + + # Load knowledge sources + knowledge_sources = load_knowledge_sources(crew_config.get("knowledge_paths", [])) + + # Create and return the crew + return Crew( + agents=list(agents.values()), + tasks=tasks, + process=Process.sequential, + planning=True, + memory=True, + knowledge_sources=knowledge_sources or None, + verbose=True, + ) diff --git a/packages/agentic-fabric/src/agentic_fabric/core/manager.py b/packages/agentic-fabric/src/agentic_fabric/core/manager.py new file mode 100644 index 0000000..c1d2b93 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/core/manager.py @@ -0,0 +1,284 @@ +"""Hierarchical Manager Agent for crew orchestration. + +This module provides a manager agent that can orchestrate multiple crews, +enabling complex workflows with smart delegation, sequential and parallel +execution, and human-in-the-loop checkpoints. + +Example: + ```python + from agentic_fabric.core.manager import ManagerAgent + + class GameDevManager(ManagerAgent): + '''Manager that orchestrates game development crews.''' + + def __init__(self): + super().__init__( + crews={ + "design": "gameplay_design", + "implementation": "ecs_implementation", + "assets": "asset_pipeline", + "qa": "qa_validation", + } + ) + + async def execute_workflow(self, task: str): + # Sequential execution + design_result = await self.delegate_async("design", task) + + # Parallel execution + impl_result, asset_result = await asyncio.gather( + self.delegate_async("implementation", design_result), + self.delegate_async("assets", design_result) + ) + + # Final QA + return await self.delegate_async("qa", { + "implementation": impl_result, + "assets": asset_result, + }) + ``` +""" + +from __future__ import annotations + +import asyncio +import logging + +from pathlib import Path +from typing import Any + +from agentic_fabric.core.decomposer import run_crew_auto +from agentic_fabric.core.discovery import discover_packages, get_crew_config + + +logger = logging.getLogger(__name__) + + +class ManagerAgent: + """Base class for hierarchical manager agents. + + A manager agent orchestrates multiple specialized crews to accomplish + complex tasks that require coordination between different domains or phases. + + Attributes: + crews: Dict mapping crew role names to crew names in packages. + package_name: Optional package name if all crews are in one package. + workspace_root: Optional workspace root for crew discovery. + """ + + def __init__( + self, + crews: dict[str, str], + package_name: str | None = None, + workspace_root: Path | None = None, + ): + """Initialize the manager agent. + + Args: + crews: Dict mapping role names to crew names (e.g., {"design": "game_design"}). + package_name: Optional package name if all crews are in the same package. + workspace_root: Optional workspace root for discovering packages. + """ + self.crews = crews + self.package_name = package_name + self.workspace_root = workspace_root + self._packages_cache: dict[str, Path] | None = None + self._crew_config_cache: dict[str, dict[str, Any]] = {} + + def _get_packages(self) -> dict[str, Path]: + """Get discovered packages, using cache if available.""" + if self._packages_cache is None: + self._packages_cache = discover_packages(self.workspace_root) + return self._packages_cache + + def delegate( + self, + crew_role: str, + inputs: dict[str, Any] | str, + framework: str | None = None, + ) -> str: + """Delegate a task to a specific crew synchronously. + + Args: + crew_role: Role name from the crews dict (e.g., "design"). + inputs: Input dict or string to pass to the crew. + framework: Optional framework override. + + Returns: + Crew output as a string. + + Raises: + ValueError: If crew_role not found in crews mapping. + """ + if crew_role not in self.crews: + raise ValueError(f"Unknown crew role '{crew_role}'. Available: {list(self.crews.keys())}") + + crew_name = self.crews[crew_role] + + # Convert string inputs to dict (create new dict to avoid mutation) + if isinstance(inputs, str): + inputs = {"task": inputs} + + # Check crew config cache first for performance + if crew_name in self._crew_config_cache: + cached_config = self._crew_config_cache[crew_name] + return run_crew_auto(cached_config, inputs=inputs, framework=framework) + + # Get crew configuration + crew_config: dict[str, Any] + if self.package_name: + # Use specified package + packages = self._get_packages() + if self.package_name not in packages: + raise ValueError(f"Package '{self.package_name}' not found. Available: {list(packages.keys())}") + crewai_dir = packages[self.package_name] + crew_config = get_crew_config(crewai_dir, crew_name) + else: + # Auto-discover package containing the crew + packages = self._get_packages() + found_config: dict[str, Any] | None = None + for pkg_dir in packages.values(): + try: + found_config = get_crew_config(pkg_dir, crew_name) + break + except ValueError: + continue + + if found_config is None: + raise ValueError( + f"Crew '{crew_name}' not found in any package. Available packages: {list(packages.keys())}" + ) + crew_config = found_config + + # Cache the config for future calls + self._crew_config_cache[crew_name] = crew_config + return run_crew_auto(crew_config, inputs=inputs, framework=framework) + + async def delegate_async( + self, + crew_role: str, + inputs: dict[str, Any] | str, + framework: str | None = None, + ) -> str: + """Delegate a task to a specific crew asynchronously. + + This runs the crew in a thread pool to avoid blocking the event loop. + + Args: + crew_role: Role name from the crews dict (e.g., "design"). + inputs: Input dict or string to pass to the crew. + framework: Optional framework override. + + Returns: + Crew output as a string. + """ + loop = asyncio.get_running_loop() + return await loop.run_in_executor(None, self.delegate, crew_role, inputs, framework) + + async def delegate_parallel( + self, + delegations: list[tuple[str, dict[str, Any] | str]], + framework: str | None = None, + ) -> list[str]: + """Delegate tasks to multiple crews in parallel. + + Args: + delegations: List of (crew_role, inputs) tuples. + framework: Optional framework override for all crews. + + Returns: + List of crew outputs in the same order as delegations. + + Example: + ```python + results = await manager.delegate_parallel([ + ("design", "Create game concept"), + ("assets", "Generate placeholder assets"), + ]) + design_result, assets_result = results + ``` + """ + tasks = [self.delegate_async(crew_role, inputs, framework) for crew_role, inputs in delegations] + return await asyncio.gather(*tasks) + + def delegate_sequential( + self, + delegations: list[tuple[str, dict[str, Any] | str]], + framework: str | None = None, + ) -> list[str]: + """Delegate tasks to multiple crews sequentially. + + Args: + delegations: List of (crew_role, inputs) tuples. + framework: Optional framework override for all crews. + + Returns: + List of crew outputs in the same order as delegations. + + Example: + ```python + results = manager.delegate_sequential([ + ("design", "Create game concept"), + ("implementation", "Implement the design"), + ("qa", "Test the implementation"), + ]) + ``` + """ + results = [] + for crew_role, inputs in delegations: + result = self.delegate(crew_role, inputs, framework) + results.append(result) + return results + + def checkpoint( + self, + message: str, + result: Any, + auto_approve: bool = False, + ) -> tuple[bool, Any]: + """Create a human-in-the-loop checkpoint. + + Args: + message: Message to display to the human reviewer. + result: Current result to review. + auto_approve: If True, automatically approve without waiting. + + Returns: + Tuple of (approved, result). If not approved, result may be modified. + + Note: + In this base implementation, checkpoints are logged but auto-approved. + Subclasses can override to implement actual HITL workflows. + """ + logger.info("=" * 60) + logger.info("CHECKPOINT: %s", message) + logger.info("=" * 60) + # Log result type only to avoid exposing sensitive data (CWE-532) + logger.info("Result type: %s", type(result).__name__) + logger.info("=" * 60) + + if auto_approve: + logger.info("Auto-approved (auto_approve=True)") + return True, result + + # Base implementation auto-approves + logger.info("Auto-approved (base implementation)") + return True, result + + async def execute_workflow(self, task: str, **kwargs: Any) -> str: + """Execute the manager's workflow. + + This is the main entry point for the manager agent. Subclasses should + override this method to implement their specific orchestration logic. + + Args: + task: The main task to accomplish. + **kwargs: Additional keyword arguments. + + Returns: + Final result as a string. + + Raises: + NotImplementedError: If not overridden by subclass. + """ + raise NotImplementedError("Subclasses must implement execute_workflow() to define their orchestration logic") diff --git a/packages/agentic-fabric/src/agentic_fabric/core/runner.py b/packages/agentic-fabric/src/agentic_fabric/core/runner.py new file mode 100644 index 0000000..2652255 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/core/runner.py @@ -0,0 +1,70 @@ +"""Runner module - executes crews with inputs.""" + +from __future__ import annotations + +from pathlib import Path + +from agentic_fabric.core.discovery import discover_packages, get_crew_config +from agentic_fabric.core.loader import load_crew_from_config + + +def run_crew( + package_name: str, + crew_name: str, + inputs: dict | None = None, + workspace_root: Path | None = None, +) -> str: + """Run a crew from a package with the given inputs. + + Args: + package_name: Name of the package (e.g., 'otterfall'). + crew_name: Name of the crew to run (e.g., 'game_builder'). + inputs: Optional dict of inputs to pass to the crew. + workspace_root: Optional workspace root path. + + Returns: + The crew's output as a string. + + Raises: + ValueError: If package or crew not found. + """ + # Discover packages + packages = discover_packages(workspace_root) + + if package_name not in packages: + available = list(packages.keys()) + raise ValueError(f"Package '{package_name}' not found. Available: {available}") + + crewai_dir = packages[package_name] + + # Load crew configuration + crew_config = get_crew_config(crewai_dir, crew_name) + + # Build the crew + crew = load_crew_from_config(crew_config) + + # Run it + result = crew.kickoff(inputs=inputs or {}) + + return result.raw if hasattr(result, "raw") else str(result) + + +def run_crew_from_path( + crewai_dir: Path, + crew_name: str, + inputs: dict | None = None, +) -> str: + """Run a crew directly from a .crewai/ directory path. + + Args: + crewai_dir: Path to the .crewai/ directory. + crew_name: Name of the crew to run. + inputs: Optional dict of inputs to pass to the crew. + + Returns: + The crew's output as a string. + """ + crew_config = get_crew_config(crewai_dir, crew_name) + crew = load_crew_from_config(crew_config) + result = crew.kickoff(inputs=inputs or {}) + return result.raw if hasattr(result, "raw") else str(result) diff --git a/packages/agentic-fabric/src/agentic_fabric/crew.py b/packages/agentic-fabric/src/agentic_fabric/crew.py new file mode 100644 index 0000000..a7e1992 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/crew.py @@ -0,0 +1,159 @@ +"""CrewAI Entry Point + +This file is the standard CrewAI entry point that `crewai run` expects. +It loads configuration from crewbase.yaml and executes the crew. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import yaml + +from crewai import Crew + + +def get_crewbase_path() -> Path: + """Get path to crewbase.yaml.""" + return Path(__file__).parent.parent.parent / "crewbase.yaml" + + +class CrewAgents: + """Wrapper class for CrewAI crew execution. + + Provides a class-based interface for flows to instantiate and run crews. + This bridges the gap between Flow expectations and the YAML-based crew config. + """ + + def __init__(self, config_path: Path | None = None): + """Initialize CrewAgents with optional custom config path.""" + self.config_path = config_path or get_crewbase_path() + self._crew: Crew | None = None + self._config: dict[str, Any] | None = None + + @property + def config(self) -> dict[str, Any]: + """Lazy load configuration.""" + if self._config is None: + with open(self.config_path) as f: + self._config = yaml.safe_load(f) + return self._config + + @property + def crew(self) -> Crew: + """Lazy load crew from YAML.""" + if self._crew is None: + self._crew = Crew.from_yaml(str(self.config_path)) # type: ignore[attr-defined] + return self._crew + + def _get_crew_for_task(self, task_name: str | None = None) -> Crew: + """Get a crew instance, optionally filtered to a specific task. + + When task filtering is needed, creates a fresh crew instance to avoid + mutating the cached crew's task list. + + Args: + task_name: Optional task name to filter to + + Returns: + Crew instance (cached if no filtering, fresh if filtering) + + Raises: + ValueError: If task_name is provided but not found + """ + if task_name is None: + return self.crew + + # Create fresh crew for task filtering to avoid mutating cached instance + crew = Crew.from_yaml(str(self.config_path)) # type: ignore[attr-defined] + matching_tasks = [t for t in crew.tasks if t.name == task_name] + + if not matching_tasks: + available = [t.name for t in crew.tasks] + raise ValueError(f"Task '{task_name}' not found. Available: {available}") + + crew.tasks = matching_tasks + return crew # type: ignore[return-value, no-any-return] + + def kickoff(self, inputs: dict[str, Any] | None = None) -> Any: + """Execute the crew with given inputs. + + Args: + inputs: Optional dict with: + - task: Specific task name to run (filters crew tasks) + - Any other inputs passed to crew.kickoff() + + Returns: + CrewOutput from crew execution + + Note: + This method does NOT mutate the inputs dict passed by caller. + """ + task_name = inputs.get("task") if inputs else None + # Create filtered inputs without mutating original + filtered_inputs = {k: v for k, v in inputs.items() if k != "task"} if inputs else None + crew = self._get_crew_for_task(task_name) + return crew.kickoff(inputs=filtered_inputs) + + def kickoff_async(self, inputs: dict[str, Any] | None = None) -> Any: + """Async version of kickoff for parallel execution. + + Note: + This method does NOT mutate the inputs dict passed by caller. + """ + task_name = inputs.get("task") if inputs else None + # Create filtered inputs without mutating original + filtered_inputs = {k: v for k, v in inputs.items() if k != "task"} if inputs else None + crew = self._get_crew_for_task(task_name) + return crew.kickoff_async(inputs=filtered_inputs) + + +def load_crewbase(): + """Load crewbase.yaml configuration.""" + crewbase_path = Path(__file__).parent.parent.parent / "crewbase.yaml" + with open(crewbase_path) as f: + return yaml.safe_load(f) + + +def kickoff(inputs: dict[str, Any] | None = None): + """Main entry point for crewai run. + + Args: + inputs: Optional dict with: + - task: Specific task name to run + - All other task inputs from crewbase.yaml + + Note: + This function does NOT mutate the inputs dict passed by caller. + """ + load_crewbase() + + # CrewAI will automatically load agents/tasks from crewbase.yaml + # The MCP tools are declared in crewbase.yaml using mcp:// syntax + crew = Crew.from_yaml(str(Path(__file__).parent.parent.parent / "crewbase.yaml")) # type: ignore[attr-defined] + + # If specific task requested, filter to that task + # Create filtered inputs without mutating original + task_name = inputs.get("task") if inputs else None + filtered_inputs = {k: v for k, v in inputs.items() if k != "task"} if inputs else None + + if task_name: + crew.tasks = [t for t in crew.tasks if t.name == task_name] + if not crew.tasks: + raise ValueError(f"Task '{task_name}' not found in crewbase.yaml") + + return crew.kickoff(inputs=filtered_inputs) + + +def train(n_iterations: int = 5, inputs: dict[str, Any] | None = None): + """Train the crew using memory/learning features.""" + load_crewbase() + crew = Crew.from_yaml(str(Path(__file__).parent.parent.parent / "crewbase.yaml")) # type: ignore[attr-defined] + + crew.train(n_iterations=n_iterations, inputs=inputs) + + +if __name__ == "__main__": + # For direct execution + kickoff() diff --git a/packages/agentic-fabric/src/agentic_fabric/crews/__init__.py b/packages/agentic-fabric/src/agentic_fabric/crews/__init__.py new file mode 100644 index 0000000..4719d9a --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/crews/__init__.py @@ -0,0 +1 @@ +"""Built-in crew definitions.""" diff --git a/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/__init__.py b/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/__init__.py new file mode 100644 index 0000000..57583ec --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/__init__.py @@ -0,0 +1 @@ +"""Connector-builder crew.""" diff --git a/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/config/agents.yaml b/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/config/agents.yaml new file mode 100644 index 0000000..31c2671 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/config/agents.yaml @@ -0,0 +1,16 @@ +doc_scraper: + role: API Documentation Scraper + goal: Extract endpoint information from online API documentation + tools: + - ScrapeWebsiteTool + - CrawlWebsiteTool + +api_analyzer: + role: API Structure Analyst + goal: Create a structured, machine-readable representation of the API from scraped documentation + +code_generator: + role: Python HTTP Client Generator + goal: Generate a vendor-fabric compatible Python HTTP client from a structured API representation + tools: + - FileWriteTool diff --git a/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/config/tasks.yaml b/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/config/tasks.yaml new file mode 100644 index 0000000..da9c510 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/config/tasks.yaml @@ -0,0 +1,14 @@ +scrape_docs: + description: Scrape the API documentation from the provided URL to extract endpoint information. + expected_output: Raw text containing API endpoint details, schemas, and authentication methods. + agent: doc_scraper + +analyze_api: + description: Analyze the raw documentation text and create a structured representation of the API. + expected_output: A JSON object detailing API endpoints, methods, parameters, and schemas. + agent: api_analyzer + +generate_code: + description: Generate Python code for a vendor-fabric compatible HTTP client based on the structured API data in the {output_dir} directory. + expected_output: A set of Python files (models.py, client.py, exceptions.py, __init__.py) in the specified output directory. + agent: code_generator diff --git a/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/connector_builder_crew.py b/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/connector_builder_crew.py new file mode 100644 index 0000000..9ae4ee4 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/connector_builder_crew.py @@ -0,0 +1,80 @@ +"""Connector Builder Crew + +This script creates and inits the 'connector_builder' crew, which is +designed to automatically generate HTTP connector code by scraping API +documentation. +""" + +from __future__ import annotations + +from pathlib import Path + +from crewai import Agent, Crew, Task + +from agentic_fabric.tools.registry import resolve_tools +from agentic_fabric.utils import load_config + + +class ConnectorBuilderCrew: + """Manages the agents and tasks for the connector builder crew. + + This class loads agent and task configurations from YAML files, + instantiates the necessary CrewAI components, and provides a method + to + execute the crew's workflow. + + Attributes: + crew: An instance of the CrewAI Crew, configured with agents and + tasks for connector building. + """ + + def __init__(self, output_dir: str = "output"): + """Initializes the ConnectorBuilderCrew. + + Loads agent and task configurations from YAML files, creates Agent + and Task objects, and assembles them into a Crew. + + Args: + output_dir: The directory where the generated connector code + will be saved. + """ + config_dir = Path(__file__).parent / "config" + agent_config = load_config(config_dir / "agents.yaml") + task_config = load_config(config_dir / "tasks.yaml") + + def build_agent(name: str) -> Agent: + config = agent_config[name].copy() + config["tools"] = resolve_tools(config.get("tools", [])) + return Agent(**config) + + # Create Agents + self.doc_scraper = build_agent("doc_scraper") + self.api_analyzer = build_agent("api_analyzer") + self.code_generator = build_agent("code_generator") + + # Create Tasks + self.scrape_docs = Task(**task_config["scrape_docs"]) + self.analyze_api = Task(**task_config["analyze_api"]) + + generate_code_config = task_config["generate_code"].copy() + generate_code_config["description"] = generate_code_config["description"].format(output_dir=output_dir) + self.generate_code = Task(**generate_code_config) + + self.crew = Crew( + agents=[self.doc_scraper, self.api_analyzer, self.code_generator], + tasks=[self.scrape_docs, self.analyze_api, self.generate_code], + verbose=2, + ) + + def kickoff(self, inputs: dict) -> str: + """Starts the crew's execution with the given inputs. + + Args: + inputs: A dictionary containing the necessary inputs for the + crew's tasks, such as the URL of the API documentation. + + Returns: + A string representing the result of the crew's execution. + """ + result = self.crew.kickoff(inputs=inputs) + return result.raw if hasattr(result, "raw") else str(result) diff --git a/packages/agentic-fabric/src/agentic_fabric/main.py b/packages/agentic-fabric/src/agentic_fabric/main.py new file mode 100644 index 0000000..ab702c4 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/main.py @@ -0,0 +1,621 @@ +"""Main entry point for agentic-fabric - Framework-Agnostic Crew Runner. + +This is a generic crew runner that discovers and executes crews +defined in packages' .crew/, .crewai/, .langgraph/, or .strands/ directories. + +Usage: + # List all available packages with crews + agentic-fabric list + agentic-fabric list --json # JSON output for external tools + + # List crews in a specific package + agentic-fabric list otterfall + + # Run a crew + agentic-fabric run otterfall game_builder --input "Create a BiomeComponent" + agentic-fabric run otterfall game_builder --input "..." --json # JSON output + + # Run with input from file + agentic-fabric run otterfall game_builder --file tasks.md + + # Show crew details + agentic-fabric info otterfall game_builder --json +""" + +from __future__ import annotations + +import argparse +import json +import sys +import time + +from pathlib import Path + +from agentic_fabric.core.discovery import discover_packages, get_crew_config, list_crews +from agentic_fabric.core.runner import run_crew + + +def cmd_list(args): + """List available packages and crews.""" + framework = getattr(args, "framework", None) + use_json = getattr(args, "json", False) + + crews_by_package = list_crews( + args.package if hasattr(args, "package") else None, + framework=framework, + ) + + if use_json: + # Flatten to list for JSON output + all_crews = [] + for pkg_name, crews in crews_by_package.items(): + for crew in crews: + all_crews.append( + { + "package": pkg_name, + "name": crew["name"], + "description": crew.get("description", ""), + "required_framework": crew.get("required_framework"), + } + ) + print(json.dumps({"crews": all_crews}, indent=2)) + return + + if not crews_by_package: + print("No packages with crew configuration directories found.") + print("\nTo add crews to a package, create one of:") + print(" packages//.crew/manifest.yaml # Framework-agnostic") + print(" packages//.crewai/manifest.yaml # CrewAI-specific") + print(" packages//.langgraph/manifest.yaml # LangGraph-specific") + print(" packages//.strands/manifest.yaml # Strands-specific") + return + + print("=" * 60) + print("AVAILABLE CREWS") + print("=" * 60) + + for pkg_name, crews in crews_by_package.items(): + print(f"\n📦 {pkg_name}") + for crew in crews: + desc = crew.get("description", "") + framework_info = "" + if crew.get("required_framework"): + framework_info = f" [{crew['required_framework']}]" + print(f" • {crew['name']}{framework_info}: {desc}") + + +def cmd_run(args): + """Run a specific crew or single-agent task.""" + use_json = getattr(args, "json", False) + start_time = time.time() + + # Check if using single-agent runner + if hasattr(args, "runner") and args.runner: + return _cmd_run_single_agent(args, use_json, start_time) + + # Multi-agent crew execution requires package and crew + if not args.package or not args.crew: + if use_json: + print( + json.dumps( + { + "success": False, + "error": "Package and crew are required for multi-agent execution. " + "Use --runner for single-agent tasks.", + "duration_ms": int((time.time() - start_time) * 1000), + } + ) + ) + else: + print("❌ Error: Package and crew are required for multi-agent execution.") + print("Use --runner for single-agent tasks or provide both package and crew.") + sys.exit(2) + + # Multi-agent crew execution (existing logic) + from agentic_fabric.core.decomposer import detect_framework, run_crew_auto + + if not use_json: + print("=" * 60) + print(f"🚀 Running {args.package}/{args.crew}") + print("=" * 60) + + # Get input + if args.file: + input_text = Path(args.file).read_text() + elif args.input: + input_text = args.input + else: + input_text = "" + + inputs = {"spec": input_text, "component_spec": input_text, "input": input_text} + + # Discover package and load config + packages = discover_packages() + if args.package not in packages: + if use_json: + print( + json.dumps( + { + "success": False, + "error": f"Package '{args.package}' not found", + "available_packages": list(packages.keys()), + "duration_ms": int((time.time() - start_time) * 1000), + } + ) + ) + else: + print(f"❌ Package '{args.package}' not found.") + print(f"Available: {list(packages.keys())}") + sys.exit(2) # Exit code 2 = configuration error + + config_dir = packages[args.package] + + try: + crew_config = get_crew_config(config_dir, args.crew) + + # Determine framework + required = crew_config.get("required_framework") + requested = args.framework if args.framework != "auto" else None + framework_used = required or requested or detect_framework() + + if not use_json: + if required: + print(f"📋 Framework: {required} (required by .{required}/ directory)") + elif requested: + print(f"📋 Framework: {requested} (requested)") + else: + print(f"📋 Framework: {framework_used} (auto-detected)") + + result = run_crew_auto( + crew_config, + inputs=inputs, + framework=requested, + ) + + duration_ms = int((time.time() - start_time) * 1000) + + if use_json: + print( + json.dumps( + { + "success": True, + "output": result, + "framework_used": framework_used, + "duration_ms": duration_ms, + } + ) + ) + else: + print("\n" + "=" * 60) + print("📄 RESULT") + print("=" * 60) + print(result) + + except (ValueError, RuntimeError) as e: + duration_ms = int((time.time() - start_time) * 1000) + if use_json: + print( + json.dumps( + { + "success": False, + "error": str(e), + "duration_ms": duration_ms, + } + ) + ) + else: + print(f"❌ Error: {e}") + sys.exit(1) # Exit code 1 = crew execution failed + + +def _cmd_run_single_agent(args, use_json: bool, start_time: float): + """Run a task with a single-agent CLI runner.""" + from agentic_fabric.core.decomposer import get_available_cli_runners, get_cli_runner + + if not use_json: + print("=" * 60) + print(f"🤖 Running single-agent: {args.runner}") + print("=" * 60) + + # Get input + if args.file: + input_text = Path(args.file).read_text() + elif args.input: + input_text = args.input + else: + if use_json: + print( + json.dumps( + { + "success": False, + "error": "No input provided. Use --input or --file", + "duration_ms": int((time.time() - start_time) * 1000), + } + ) + ) + else: + print("❌ Error: No input provided. Use --input or --file") + sys.exit(2) + + # Get working directory if package specified + working_dir = None + if hasattr(args, "package") and args.package: + packages = discover_packages() + if args.package in packages: + # Use package directory as working dir + working_dir = str(packages[args.package].parent) + + try: + # Get runner + runner = get_cli_runner( + args.runner, + model=getattr(args, "model", None), + ) + + # Check if available + if not runner.is_available(): + available = get_available_cli_runners() + if use_json: + print( + json.dumps( + { + "success": False, + "error": f"Runner '{args.runner}' not available (tool not installed)", + "available_runners": available, + "duration_ms": int((time.time() - start_time) * 1000), + } + ) + ) + else: + print(f"❌ Runner '{args.runner}' not available (tool not installed)") + print(f"\nAvailable runners: {', '.join(available)}") + print("\nTo install, check: agentic-fabric list-runners --json") + sys.exit(2) + + if not use_json: + runner_label = getattr(getattr(runner, "config", None), "name", None) or args.runner + print(f"📋 Runner: {runner_label}") + if working_dir: + print(f"📁 Working dir: {working_dir}") + + # Run the task + result = runner.run( + task=input_text, + working_dir=working_dir, + auto_approve=getattr(args, "auto_approve", True), + ) + + duration_ms = int((time.time() - start_time) * 1000) + + if use_json: + print( + json.dumps( + { + "success": True, + "output": result, + "runner": args.runner, + "duration_ms": duration_ms, + } + ) + ) + else: + print("\n" + "=" * 60) + print("📄 RESULT") + print("=" * 60) + print(result) + + except (ValueError, RuntimeError, FileNotFoundError) as e: + duration_ms = int((time.time() - start_time) * 1000) + if use_json: + print( + json.dumps( + { + "success": False, + "error": str(e), + "duration_ms": duration_ms, + } + ) + ) + else: + print(f"❌ Error: {e}") + sys.exit(1) + + +def cmd_build(args): + """Legacy build command - runs otterfall game_builder.""" + print("=" * 60) + print("🎮 OTTERFALL GAME BUILDER") + print("=" * 60) + print() + print(f"Building: {args.spec[:100]}...") + + inputs = {"spec": args.spec, "component_spec": args.spec} + + try: + result = run_crew("otterfall", "game_builder", inputs) + print("\n" + "=" * 60) + print("📄 RESULT") + print("=" * 60) + print(result) + except ValueError as e: + print(f"❌ Error: {e}") + print("\nNote: The 'build' command requires packages/otterfall/.crewai/") + sys.exit(1) + + +def cmd_info(args): + """Show detailed info about a crew.""" + use_json = getattr(args, "json", False) + packages = discover_packages() + + if args.package not in packages: + if use_json: + print( + json.dumps( + { + "error": f"Package '{args.package}' not found", + "available_packages": list(packages.keys()), + } + ) + ) + else: + print(f"❌ Package '{args.package}' not found.") + print(f"Available: {list(packages.keys())}") + sys.exit(2) + + config_dir = packages[args.package] + + try: + config = get_crew_config(config_dir, args.crew) + except ValueError as e: + if use_json: + print(json.dumps({"error": str(e)})) + else: + print(f"❌ {e}") + sys.exit(2) + + if use_json: + print( + json.dumps( + { + "package": args.package, + "name": args.crew, + "description": config.get("description", ""), + "required_framework": config.get("required_framework"), + "agents": [ + {"name": name, "role": cfg.get("role", name)} for name, cfg in config.get("agents", {}).items() + ], + "tasks": [ + {"name": name, "description": cfg.get("description", "")} + for name, cfg in config.get("tasks", {}).items() + ], + "knowledge_paths": config.get("knowledge_paths", []), + }, + indent=2, + ) + ) + return + + print("=" * 60) + print(f"CREW: {args.package}/{args.crew}") + print("=" * 60) + print(f"\nDescription: {config.get('description', 'N/A')}") + + print("\n📋 Agents:") + for name, cfg in config.get("agents", {}).items(): + role = cfg.get("role", name) + print(f" • {name}: {role}") + + print("\n📝 Tasks:") + for name, cfg in config.get("tasks", {}).items(): + desc = cfg.get("description", "")[:60] + print(f" • {name}: {desc}...") + + print("\n📚 Knowledge:") + for kp in config.get("knowledge_paths", []): + print(f" • {kp}") + + +def cmd_list_runners(args): + """List available single-agent CLI runners.""" + from agentic_fabric.core.decomposer import ( + get_available_cli_runners, + get_cli_runner, + ) + + use_json = getattr(args, "json", False) + + try: + profiles = get_available_cli_runners() + + if use_json: + runners_info = [] + for profile in profiles: + try: + runner = get_cli_runner(profile) + runners_info.append( + { + "name": profile, + "display_name": runner.config.name, + "description": runner.config.description, + "available": runner.is_available(), + "install_cmd": runner.config.install_cmd, + "docs_url": runner.config.docs_url, + "required_env": runner.get_required_env_vars(), + } + ) + except Exception as e: + # Skip profiles that fail to load, but warn the user + print( + f"Warning: Could not load profile '{profile}': {e}", + file=sys.stderr, + ) + continue + + print(json.dumps({"runners": runners_info}, indent=2)) + else: + print("=" * 60) + print("AVAILABLE SINGLE-AGENT CLI RUNNERS") + print("=" * 60) + print() + + for profile in profiles: + try: + runner = get_cli_runner(profile) + available = "✅" if runner.is_available() else "❌" + print(f"{available} {profile}: {runner.config.description}") + + if not runner.is_available(): + print(f" Install: {runner.config.install_cmd}") + + if runner.get_required_env_vars(): + print(f" Requires: {', '.join(runner.get_required_env_vars())}") + + print() + except Exception as e: + # Skip profiles that fail to load, but warn the user + print( + f"Warning: Could not load profile '{profile}': {e}", + file=sys.stderr, + ) + continue + + except FileNotFoundError as e: + if use_json: + print(json.dumps({"error": str(e)})) + else: + print(f"❌ Error: {e}") + sys.exit(2) + + +def main(): + """CLI entry point.""" + parser = argparse.ArgumentParser( + description="agentic-fabric - Framework-Agnostic Crew Runner", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # List all packages with crews + agentic-fabric list + agentic-fabric list --json # JSON output for external tools + + # List crews in a package + agentic-fabric list otterfall + + # List available single-agent runners + agentic-fabric list-runners + agentic-fabric list-runners --json + + # Run a multi-agent crew + agentic-fabric run otterfall game_builder --input "Create a QuestComponent" + agentic-fabric run otterfall game_builder --input "..." --json # JSON output + + # Run with single-agent CLI runner + agentic-fabric run --runner aider --input "Add error handling to auth.py" + agentic-fabric run --runner claude-code --input "Refactor the database module" + agentic-fabric run --runner ollama --input "Fix the bug" --model deepseek-coder + + # Show crew details + agentic-fabric info otterfall game_builder --json + +Exit codes: + 0 - Success + 1 - Crew execution failed + 2 - Configuration error (package/crew not found) + """, + ) + + subparsers = parser.add_subparsers(dest="command", help="Available commands") + + # List command + list_parser = subparsers.add_parser("list", help="List available crews") + list_parser.add_argument("package", nargs="?", help="Package to list crews for") + list_parser.add_argument( + "--framework", + choices=["crewai", "langgraph", "strands"], + help="Filter crews by framework", + ) + list_parser.add_argument("--json", action="store_true", help="Output as JSON (for external tools)") + + # List runners command + list_runners_parser = subparsers.add_parser("list-runners", help="List available single-agent CLI runners") + list_runners_parser.add_argument("--json", action="store_true", help="Output as JSON (for external tools)") + + # Run command + run_parser = subparsers.add_parser("run", help="Run a crew or single-agent task") + run_parser.add_argument("package", nargs="?", help="Package name (e.g., otterfall)") + run_parser.add_argument("crew", nargs="?", help="Crew name (e.g., game_builder)") + run_parser.add_argument("--input", "-i", help="Input specification") + run_parser.add_argument("--file", "-f", help="Read input from file") + run_parser.add_argument( + "--framework", + choices=["auto", "crewai", "langgraph", "strands"], + default="auto", + help="Framework to use (auto=detect, or specify). " + "Note: If crew is in a framework-specific directory, that takes precedence.", + ) + run_parser.add_argument( + "--runner", + help="Single-agent CLI runner to use (e.g., aider, claude-code, ollama). " + "When specified, package/crew are optional and task is run with the CLI tool.", + ) + run_parser.add_argument( + "--model", + help="Model to use with single-agent runner (if supported by the tool)", + ) + run_parser.add_argument( + "--auto-approve", + action=argparse.BooleanOptionalAction, + default=True, + help="Auto-approve changes (default: true). Use --no-auto-approve to disable.", + ) + run_parser.add_argument("--json", action="store_true", help="Output as JSON (for external tools)") + + # Info command + info_parser = subparsers.add_parser("info", help="Show crew details") + info_parser.add_argument("package", help="Package name") + info_parser.add_argument("crew", help="Crew name") + info_parser.add_argument("--json", action="store_true", help="Output as JSON (for external tools)") + + # Legacy build command (for backwards compatibility) + build_parser = subparsers.add_parser("build", help="Build a game component (legacy)") + build_parser.add_argument("spec", help="Component specification") + + # Legacy commands + subparsers.add_parser("list-knowledge", help="List knowledge sources (legacy)") + subparsers.add_parser("test-tools", help="Test file tools (legacy)") + + args = parser.parse_args() + + if args.command == "list": + cmd_list(args) + elif args.command == "list-runners": + cmd_list_runners(args) + elif args.command == "run": + cmd_run(args) + elif args.command == "info": + cmd_info(args) + elif args.command == "build": + cmd_build(args) + elif args.command == "list-knowledge": + # Legacy - list knowledge from otterfall + packages = discover_packages() + if "otterfall" in packages: + config = get_crew_config(packages["otterfall"], "game_builder") + print("Knowledge sources:") + for kp in config.get("knowledge_paths", []): + print(f" • {kp}") + else: + print("No otterfall package found.") + elif args.command == "test-tools": + from agentic_fabric.tools.file_tools import DirectoryListTool, get_workspace_root + + print(f"Workspace root: {get_workspace_root()}") + tool = DirectoryListTool() + print(tool._run("packages")) + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/packages/agentic-fabric/src/agentic_fabric/run_flow.py b/packages/agentic-fabric/src/agentic_fabric/run_flow.py new file mode 100644 index 0000000..1a5db50 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/run_flow.py @@ -0,0 +1,128 @@ +"""Utility to run CrewAI flows from command line.""" + +from __future__ import annotations + +import importlib +import sys + +from typing import Any, cast + + +def _load_flow(module_name: str, class_name: str) -> type[Any]: + module = importlib.import_module(module_name) + return cast(type[Any], getattr(module, class_name)) + + +def run_tdd_prototype(requirements: dict[str, Any]) -> Any: + """Run TDD Prototype Flow.""" + flow_cls = _load_flow("agentic_fabric.flows.tdd_prototype_flow", "TDDPrototypeFlow") + flow = flow_cls() + return flow.kickoff(inputs={"requirements": requirements}) + + +def run_meshy_asset(species: str, prompt: str, retexture_prompt: str) -> Any: + """Run Meshy Asset Flow.""" + flow_cls = _load_flow("agentic_fabric.flows.meshy_asset_flow", "MeshyAssetFlow") + flow = flow_cls() + return flow.kickoff(inputs={"species": species, "prompt": prompt, "retexture_prompt": retexture_prompt}) + + +def run_prototype_assessment(prototypes: list[Any]) -> Any: + """Run Prototype to Production Flow.""" + flow_cls = _load_flow( + "agentic_fabric.flows.prototype_to_production_flow", + "PrototypeToProductionFlow", + ) + flow = flow_cls() + return flow.kickoff(inputs={"prototypes": prototypes}) + + +def run_asset_integration(asset_manifest: dict[str, Any]) -> Any: + """Run Asset Integration Flow.""" + flow_cls = _load_flow("agentic_fabric.flows.asset_integration_flow", "AssetIntegrationFlow") + flow = flow_cls() + return flow.kickoff(inputs={"asset_manifest": asset_manifest}) + + +def run_hitl_review(content_type: str, content_url: str) -> Any: + """Run HITL Review Flow.""" + flow_cls = _load_flow("agentic_fabric.flows.hitl_review_flow", "HITLReviewFlow") + flow = flow_cls() + return flow.kickoff(inputs={"content_type": content_type, "content_url": content_url}) + + +def run_batch_generation(species_list: list[str]) -> Any: + """Run Batch Generation Flow.""" + flow_cls = _load_flow("agentic_fabric.flows.batch_generation_flow", "BatchGenerationFlow") + flow = flow_cls() + return flow.kickoff(inputs={"species_list": species_list}) + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python -m crew_agents.run_flow [args...]") + print("\nAvailable flows:") + print(" tdd_prototype") + print(" meshy_asset ") + print(" prototype_assessment ...") + print(" asset_integration") + print(" hitl_review ") + print(" batch_generation ...") + sys.exit(1) + + flow_name = sys.argv[1] + + # Validate flow name + valid_flows = [ + "tdd_prototype", + "meshy_asset", + "prototype_assessment", + "asset_integration", + "hitl_review", + "batch_generation", + ] + if flow_name not in valid_flows: + print(f"❌ Unknown flow: {flow_name}") + print(f"Valid flows: {', '.join(valid_flows)}") + sys.exit(1) + + try: + if flow_name == "tdd_prototype": + requirements = {"feature": "biome_selector"} + result = run_tdd_prototype(requirements) + print("\n✅ Flow completed successfully") + print(f"Result: {result}") + + elif flow_name == "meshy_asset": + species = sys.argv[2] if len(sys.argv) > 2 else "otter" + prompt = sys.argv[3] if len(sys.argv) > 3 else "A realistic otter" + retexture = sys.argv[4] if len(sys.argv) > 4 else "grey fur variant" + run_meshy_asset(species, prompt, retexture) + + elif flow_name == "prototype_assessment": + prototypes = sys.argv[2:] if len(sys.argv) > 2 else ["biome_selector_diorama"] + run_prototype_assessment(prototypes) + + elif flow_name == "asset_integration": + manifest = { + "species": sys.argv[2] if len(sys.argv) > 2 else "otter", + "glb_url": sys.argv[3] if len(sys.argv) > 3 else "", + "animations": [], + } + run_asset_integration(manifest) + + elif flow_name == "hitl_review": + content_type = sys.argv[2] if len(sys.argv) > 2 else "asset" + content_url = sys.argv[3] if len(sys.argv) > 3 else "" + run_hitl_review(content_type, content_url) + + elif flow_name == "batch_generation": + species_list = sys.argv[2:] if len(sys.argv) > 2 else ["otter", "beaver", "muskrat"] + run_batch_generation(species_list) + + except Exception as e: + print(f"\n❌ Flow execution failed: {e!s}") + import traceback + + traceback.print_exc() + sys.exit(1) diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/__init__.py b/packages/agentic-fabric/src/agentic_fabric/runners/__init__.py new file mode 100644 index 0000000..55b9448 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/runners/__init__.py @@ -0,0 +1,32 @@ +"""Framework-specific runners for agentic-fabric. + +Each runner implements the same interface but targets a different AI framework: +- CrewAIRunner: Full-featured, best for complex crews +- LangGraphRunner: Graph-based flows, good for conditional logic +- StrandsRunner: Lightweight, AWS-native + +Single-agent runners provide simpler execution without multi-agent overhead: +- LocalCLIRunner: Universal runner for any CLI-based coding agent (aider, claude-code, ollama, etc.) + +Usage: + # Multi-agent crews + from agentic_fabric.runners import get_runner + + runner = get_runner("crewai") # Or "langgraph", "strands", "auto" + crew = runner.build_crew(config) + result = runner.run(crew, inputs) + + # Single-agent CLI runners + from agentic_fabric.core.decomposer import get_cli_runner + + runner = get_cli_runner("aider") + result = runner.run("Add error handling to auth.py") +""" + +from __future__ import annotations + +from agentic_fabric.core.decomposer import get_cli_runner, get_framework_info, get_runner +from agentic_fabric.runners.registry import RuntimeSpec, runtime_names, runtime_specs + + +__all__ = ["RuntimeSpec", "get_cli_runner", "get_framework_info", "get_runner", "runtime_names", "runtime_specs"] diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/base.py b/packages/agentic-fabric/src/agentic_fabric/runners/base.py new file mode 100644 index 0000000..9e16902 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/runners/base.py @@ -0,0 +1,370 @@ +"""Base runner interface for framework-specific implementations. + +All runners must implement this interface to ensure consistent behavior +across CrewAI, LangGraph, and Strands. + +# Runner Implementation Guide + +To create a new runner for a framework: + +1. **Subclass BaseRunner**: + ```python + from agentic_fabric.runners.base import BaseRunner + + class MyFrameworkRunner(BaseRunner): + framework_name = "myframework" + ``` + +2. **Implement required methods**: + - `build_crew()` - Convert universal config to framework crew + - `run()` - Execute the crew and return string output + - `build_agent()` - Create framework-specific agent + - `build_task()` - Create framework-specific task + +3. **Register in decomposer** (core/decomposer.py): + - Add to `FRAMEWORK_PRIORITY` + - Add case to `get_runner()` + +4. **Add optional dependency** (pyproject.toml): + ```toml + [project.optional-dependencies] + myframework = ["myframework-package>=1.0"] + ``` + +# Example Runner + +```python +class SimpleRunner(BaseRunner): + framework_name = "simple" + + def __init__(self): + try: + import simple_framework + except ImportError as e: + raise RuntimeError("simple_framework not installed") from e + + def build_crew(self, crew_config): + # Use dict to allow lookup by agent name + agents = { + name: self.build_agent(cfg) + for name, cfg in crew_config.get("agents", {}).items() + } + + tasks = [] + for task_name, task_cfg in crew_config.get("tasks", {}).items(): + agent_name = task_cfg.get("agent") + agent = agents[agent_name] + tasks.append(self.build_task(task_cfg, agent)) + + return {"agents": list(agents.values()), "tasks": tasks} + + def run(self, crew, inputs): + # Execute and return string + return "Result from simple framework" + + def build_agent(self, agent_config, tools=None): + return {"role": agent_config["role"], "tools": tools or []} + + def build_task(self, task_config, agent): + return {"description": task_config["description"], "agent": agent} +``` +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Any + +from agentic_fabric.capabilities import AgentCapabilityProviderMixin, runtime_capability + + +class BaseRunner(AgentCapabilityProviderMixin, ABC): + """Abstract base class for framework runners. + + Each framework runner converts agentic-fabric's universal crew format + into framework-specific objects and executes them. + + Attributes: + framework_name: String identifier for this framework (e.g., "crewai", + "langgraph", "strands"). Used for logging and framework selection. + + Example: + Basic runner usage with auto-detection: + + ```python + from agentic_fabric.core.decomposer import get_runner + + # Auto-detect best available framework + runner = get_runner() + + crew_config = { + "name": "research_crew", + "agents": { + "researcher": { + "role": "Senior Researcher", + "goal": "Find comprehensive information", + "backstory": "Expert researcher with years of experience" + } + }, + "tasks": { + "research_task": { + "description": "Research the topic thoroughly", + "expected_output": "Detailed research report", + "agent": "researcher" + } + } + } + + # Build and run in one step + result = runner.build_and_run(crew_config, {"topic": "AI safety"}) + print(result) + ``` + """ + + framework_name: str = "base" + + @runtime_capability( + "build_crew", + description="Convert universal crew configuration into a runtime-native crew object.", + ) + @abstractmethod + def build_crew(self, crew_config: dict[str, Any]) -> Any: + """Build a framework-specific crew from configuration. + + This is the main method that converts agentic-fabric's universal + YAML-based crew configuration into the framework's native objects. + + Args: + crew_config: Universal crew configuration dict containing: + - name (str): Crew name for identification + - description (str): What the crew does + - agents (dict): Dict mapping agent names to agent configs. + Each agent config has: + - role (str): Agent's role/title + - goal (str): What the agent aims to achieve + - backstory (str): Agent's background/expertise + - tools (list[str], optional): Tool names to enable + - llm (str, optional): Model override + - allow_delegation (bool, optional): Can delegate to others + - tasks (dict): Dict mapping task names to task configs. + Each task config has: + - description (str): What to do + - expected_output (str): What output looks like + - agent (str): Name of agent to execute this task + - context (list[str], optional): Names of prior tasks + - knowledge_paths (list[Path], optional): Directories with + knowledge files (.md, .txt, etc.) + - process (str, optional): "sequential" or "hierarchical" + - required_framework (str, optional): Enforced framework + + Returns: + Framework-specific crew object. Type varies by framework: + - CrewAI: crewai.Crew + - LangGraph: Compiled StateGraph + - Strands: strands.Agent + + Raises: + ValueError: If configuration is invalid (e.g., unknown agent + referenced in task). + RuntimeError: If framework is not available. + + Example: + ```python + runner = CrewAIRunner() + crew = runner.build_crew({ + "name": "writer_crew", + "agents": { + "writer": { + "role": "Technical Writer", + "goal": "Create clear documentation", + "backstory": "10 years writing experience" + } + }, + "tasks": { + "write_docs": { + "description": "Write API documentation for {topic}", + "expected_output": "Markdown documentation file", + "agent": "writer" + } + } + }) + ``` + """ + + @runtime_capability("run", description="Execute a runtime-native crew object.") + @abstractmethod + def run(self, crew: Any, inputs: dict[str, Any]) -> str: + r"""Execute the crew with inputs. + + Runs the previously built crew with the given inputs. This method + handles the framework-specific execution and normalizes the output + to a string. + + Args: + crew: Framework-specific crew object from build_crew(). + Type depends on framework: + - CrewAI: crewai.Crew instance + - LangGraph: Compiled StateGraph + - Strands: strands.Agent instance + inputs: Input dict to pass to the crew. Keys are typically + variable names used in task descriptions (e.g., {topic} + in description becomes inputs["topic"]). + + Returns: + Crew output as a string. For complex outputs, the raw text + is returned; parsing is left to the caller. + + Raises: + RuntimeError: If crew execution fails. + + Example: + ```python + # CrewAI runner example + result = runner.run(crew, {"topic": "machine learning"}) + print(result) # "## Machine Learning Report\\n\\n..." + + # LangGraph runner example + result = runner.run(graph, {"input": "analyze this code"}) + ``` + """ + + @runtime_capability("build_and_run", aliases=("run_crew",), description="Build and execute a crew in one call.") + def build_and_run( + self, + crew_config: dict[str, Any], + inputs: dict[str, Any] | None = None, + ) -> str: + """Convenience method to build and run in one step. + + Args: + crew_config: Crew configuration. + inputs: Optional inputs for the crew. + + Returns: + Crew output as string. + """ + crew = self.build_crew(crew_config) + return self.run(crew, inputs or {}) + + @runtime_capability("build_agent", description="Create one runtime-native agent object.") + @abstractmethod + def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: + """Build a framework-specific agent. + + Creates a single agent from the universal configuration. Called by + build_crew() for each agent in the crew. + + Args: + agent_config: Agent configuration dict containing: + - role (str): Agent's role/title (e.g., "Senior Researcher") + - goal (str): What the agent aims to achieve + - backstory (str): Agent's background and expertise + - llm (str, optional): Model name to override default + - allow_delegation (bool, optional): Can delegate tasks + - verbose (bool, optional): Enable verbose output + tools: Optional list of tool instances to give the agent. + Tools should already be resolved/instantiated. + + Returns: + Framework-specific agent object: + - CrewAI: crewai.Agent + - LangGraph: Compiled ReAct agent graph + - Strands: strands.Agent + + Example: + ```python + # CrewAI + agent = runner.build_agent({ + "role": "Data Analyst", + "goal": "Analyze data and provide insights", + "backstory": "Expert in statistical analysis" + }, tools=[search_tool, calculator_tool]) + ``` + """ + + @runtime_capability("build_task", description="Create one runtime-native task object.") + @abstractmethod + def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: + """Build a framework-specific task. + + Creates a single task from the universal configuration. Called by + build_crew() for each task in the crew. + + Note: Some frameworks (like CrewAI) support additional parameters + beyond the base signature. Subclasses may extend this method with + optional parameters like `context` for task dependencies. + + Args: + task_config: Task configuration dict containing: + - description (str): What the task should accomplish. + Can include {placeholders} for runtime inputs. + - expected_output (str): Description of expected output format + - async_execution (bool, optional): Run asynchronously + - human_input (bool, optional): Request human feedback + agent: Framework-specific agent to assign to this task. + Must be the return value from build_agent(). + + Returns: + Framework-specific task object: + - CrewAI: crewai.Task + - LangGraph: dict (tasks are stored as config) + - Strands: dict (tasks are stored as config) + + Example: + ```python + # Basic task + task = runner.build_task({ + "description": "Research {topic} thoroughly", + "expected_output": "Comprehensive research report" + }, agent=researcher_agent) + + # CrewAI with context (extended signature) + task = crewai_runner.build_task( + task_config, + agent, + context=[prior_research_task] # CrewAI-specific + ) + ``` + """ + + @runtime_capability("get_llm", description="Return the runtime-compatible LLM object.") + def get_llm(self, model: str | None = None) -> Any: + """Get the LLM for this framework. + + Retrieves or creates an LLM instance. The default implementation + uses agentic-fabric's shared LLM configuration. Subclasses may + override for framework-specific LLM handling. + + Args: + model: Optional model name override. If None, uses DEFAULT_MODEL + from agentic_fabric.config.llm (usually claude-sonnet-4-20250514). + + Returns: + Framework-specific LLM object, or None if the LLM module + is not available (framework may have its own default). + - CrewAI: LiteLLM-compatible LLM + - LangGraph: langchain ChatModel + - Strands: Uses model_id directly + + Example: + ```python + # Use default model + llm = runner.get_llm() + + # Use specific model + llm = runner.get_llm("claude-haiku-4-5-20251001") + + # In LangGraphRunner (overridden) + llm = runner.get_llm("claude-sonnet-4-20250514") # Returns ChatAnthropic + ``` + """ + # Default implementation - subclasses can override + # Import lazily to avoid requiring crewai at module load time + try: + from agentic_fabric.config.llm import DEFAULT_MODEL, get_llm + + # Use default model if none specified to avoid AttributeError + return get_llm(model or DEFAULT_MODEL) + except ImportError: + # If llm module not available, return None (framework may have its own default) + return None diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py new file mode 100644 index 0000000..e22f682 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py @@ -0,0 +1,195 @@ +"""CrewAI runner implementation. + +This is the default and most full-featured runner. CrewAI provides: +- Hierarchical and sequential processes +- Memory and planning +- Knowledge sources +- Tool integration +- Delegation between agents +""" + +from __future__ import annotations + +import logging + +from pathlib import Path +from typing import Any + +from agentic_fabric.runners.base import BaseRunner +from agentic_fabric.tools.registry import resolve_tools + + +logger = logging.getLogger(__name__) + + +class CrewAIRunner(BaseRunner): + """Runner that uses CrewAI for crew execution.""" + + framework_name = "crewai" + + def __init__(self): + """Initialize CrewAI runner.""" + # Verify CrewAI is available + try: + import crewai # noqa: F401 + except ImportError as e: + raise RuntimeError("CrewAI not installed. Install with: pip install crewai[tools]") from e + + def build_crew(self, crew_config: dict[str, Any]) -> Any: + """Build a CrewAI Crew from configuration. + + Args: + crew_config: Universal crew configuration. + + Returns: + CrewAI Crew object. + """ + from crewai import Crew, Process + + # Build agents with their tools from config + agents_config = crew_config.get("agents", {}) + agents = {} + for agent_name, agent_cfg in agents_config.items(): + # Extract tools from agent config + agent_tools = self._resolve_tools(agent_cfg.get("tools", [])) + agents[agent_name] = self.build_agent(agent_cfg, tools=agent_tools) + + # Build tasks, tracking them for context dependencies + tasks_config = crew_config.get("tasks", {}) + tasks = [] + tasks_by_name: dict[str, Any] = {} + for task_name, task_cfg in tasks_config.items(): + agent_name = task_cfg.get("agent") + if not agent_name or agent_name not in agents: + raise ValueError(f"Task '{task_name}' has invalid agent: {agent_name}") + + # Build context from referenced tasks + context_tasks = [] + for ctx_name in task_cfg.get("context", []): + if ctx_name in tasks_by_name: + context_tasks.append(tasks_by_name[ctx_name]) + + task = self.build_task(task_cfg, agents[agent_name], context=context_tasks) + tasks.append(task) + tasks_by_name[task_name] = task + + # Load knowledge sources + knowledge_sources = self._load_knowledge(crew_config.get("knowledge_paths", [])) + + # Determine process type + process_type = crew_config.get("process", "sequential") + process = Process.hierarchical if process_type == "hierarchical" else Process.sequential + + return Crew( + agents=list(agents.values()), + tasks=tasks, + process=process, + planning=crew_config.get("planning", True), + memory=crew_config.get("memory", True), + knowledge_sources=knowledge_sources or None, + verbose=crew_config.get("verbose", True), + ) + + def run(self, crew: Any, inputs: dict[str, Any]) -> str: + """Execute the CrewAI crew. + + Args: + crew: CrewAI Crew object. + inputs: Inputs for the crew. + + Returns: + Crew output as string. + """ + result = crew.kickoff(inputs=inputs) + return result.raw if hasattr(result, "raw") else str(result) + + def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: + """Build a CrewAI Agent. + + Args: + agent_config: Agent configuration. + tools: Optional tools for the agent. + + Returns: + CrewAI Agent object. + """ + from crewai import Agent + + return Agent( + role=agent_config.get("role", "Agent"), + goal=agent_config.get("goal", ""), + backstory=agent_config.get("backstory", ""), + llm=self.get_llm(agent_config.get("llm")), + tools=tools or [], + allow_delegation=agent_config.get("allow_delegation", False), + verbose=True, + ) + + def build_task( + self, + task_config: dict[str, Any], + agent: Any, + context: list | None = None, + ) -> Any: + """Build a CrewAI Task. + + Args: + task_config: Task configuration. + agent: Agent to assign to the task. + context: Optional list of tasks this task depends on. + + Returns: + CrewAI Task object. + """ + from crewai import Task + + task_kwargs = { + "description": task_config.get("description", ""), + "expected_output": task_config.get("expected_output", ""), + "agent": agent, + } + + # Add context if provided (for task chaining) + if context: + task_kwargs["context"] = context + + return Task(**task_kwargs) + + def _resolve_tools(self, tool_names: list[str]) -> list: + """Resolve tool names to actual tool instances. + + Args: + tool_names: List of tool names from config. + + Returns: + List of tool instances. + """ + return resolve_tools(tool_names) + + def _load_knowledge(self, knowledge_paths: list[Path]) -> list: + """Load knowledge sources from paths. + + Args: + knowledge_paths: List of knowledge directory paths. + + Returns: + List of CrewAI knowledge source objects. + """ + from crewai.knowledge.source.text_file_knowledge_source import TextFileKnowledgeSource + + sources = [] + for knowledge_path_value in knowledge_paths: + knowledge_path = knowledge_path_value if isinstance(knowledge_path_value, Path) else Path(knowledge_path_value) + if not knowledge_path.exists(): + continue + + for ext in ["*.md", "*.txt", "*.py", "*.ts"]: + for file_path in knowledge_path.rglob(ext): + try: + content = file_path.read_text(encoding="utf-8") + if content.strip(): + sources.append(TextFileKnowledgeSource(file_paths=[str(file_path)])) + except OSError as exc: + logger.warning("Could not load knowledge file %s: %s", file_path, exc) + + return sources diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py new file mode 100644 index 0000000..ca5f8bd --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py @@ -0,0 +1,135 @@ +"""LangGraph runner implementation. + +LangGraph excels at: +- Complex conditional flows +- State management +- Cycles and loops +- Integration with LangChain ecosystem +""" + +from __future__ import annotations + +from typing import Any + +from agentic_fabric.runners.base import BaseRunner +from agentic_fabric.tools.adapters import resolve_langgraph_tools + + +class LangGraphRunner(BaseRunner): + """Runner that uses LangGraph for crew execution.""" + + framework_name = "langgraph" + + def __init__(self): + """Initialize LangGraph runner.""" + try: + import langgraph # noqa: F401 + except ImportError as e: + raise RuntimeError("LangGraph not installed. Install with: pip install langgraph") from e + + def build_crew(self, crew_config: dict[str, Any]) -> Any: + """Build a LangGraph workflow from configuration. + + Converts crew format to a LangGraph StateGraph with agents as nodes + and tasks as edges. + + Args: + crew_config: Universal crew configuration. + + Returns: + Compiled LangGraph StateGraph. + """ + from langgraph.prebuilt import create_react_agent + + # For simple crews, create a ReAct agent + # More complex crews could be converted to full StateGraphs + + # Get LLM from config (respects the framework's configuration) + llm_config = crew_config.get("llm", {}) + model = llm_config.get("model") if isinstance(llm_config, dict) else llm_config + llm = self.get_llm(model) + + # Build tools declared by agents in the universal config + tools = self._build_tools_from_config(crew_config) + + return create_react_agent(llm, tools) + + def run(self, crew: Any, inputs: dict[str, Any]) -> str: + """Execute the LangGraph workflow. + + Args: + crew: Compiled LangGraph. + inputs: Inputs for the workflow. + + Returns: + Workflow output as string. + """ + # Convert inputs to messages format + user_message = inputs.get("input", inputs.get("task", str(inputs))) + + result = crew.invoke({"messages": [("user", user_message)]}) + + # Extract final message + messages = result.get("messages", []) + if messages: + final = messages[-1] + return final.content if hasattr(final, "content") else str(final) + + return str(result) + + def get_llm(self, model: str | None = None) -> Any: + """Get LangChain-compatible LLM. + + Args: + model: Optional model name override. + + Returns: + LangChain ChatAnthropic LLM. + """ + from langchain_anthropic import ChatAnthropic + + # Default to Claude 3.5 Sonnet if no model specified + default_model = "claude-haiku-4-5-20251001" + return ChatAnthropic(model=model or default_model) + + def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: + """Build a LangGraph-compatible agent. + + Args: + agent_config: Agent configuration. + tools: Optional tools. + + Returns: + LangGraph agent. + """ + from langgraph.prebuilt import create_react_agent + + # Get LLM from agent config if specified + llm = self.get_llm(agent_config.get("llm")) + return create_react_agent(llm, tools or []) + + def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: + """Build a task representation for LangGraph. + + In LangGraph, tasks are typically represented as graph nodes or + prompts to agents. Returns a dict for now. + + Args: + task_config: Task configuration. + agent: Agent for the task. + + Returns: + Task configuration dict with agent reference. + """ + return { + "description": task_config.get("description", ""), + "expected_output": task_config.get("expected_output", ""), + "agent": agent, + } + + def _build_tools_from_config(self, crew_config: dict[str, Any]) -> list: + """Resolve configured agent tools into LangGraph-compatible tools.""" + tool_names: list[str] = [] + for agent_cfg in crew_config.get("agents", {}).values(): + tool_names.extend(agent_cfg.get("tools", [])) + return resolve_langgraph_tools(tool_names) diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_profiles.yaml b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_profiles.yaml new file mode 100644 index 0000000..f0711a3 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_profiles.yaml @@ -0,0 +1,169 @@ +# Local CLI Runner Profiles +# Verified CLI interfaces for single-agent coding tools +# +# These profiles define how to invoke various CLI coding agents. +# All flags verified from official documentation. + +profiles: + # ============================================ + # Aider - AI Pair Programming + # Docs: https://aider.chat/docs/config/options.html + # ============================================ + aider: + name: "Aider" + description: "AI pair programming in your terminal" + command: "aider" + task_flag: "--message" + auth_env: + - "OPENAI_API_KEY" + - "ANTHROPIC_API_KEY" + auto_approve: "--yes-always" + model_flag: "--model" + additional_flags: + - "--no-git" # Let agentic-fabric handle git + install_cmd: "pipx install aider-chat" + docs_url: "https://aider.chat/docs/config/options.html" + notes: "Supports OpenAI, Anthropic, Gemini, Ollama, and more" + + # ============================================ + # Claude Code - Anthropic's Coding Agent + # Docs: https://docs.anthropic.com/en/docs/claude-code/cli-usage + # ============================================ + claude-code: + name: "Claude Code" + description: "Anthropic's AI coding agent" + command: "claude" + task_flag: "--print" # -p also works + auth_env: + - "ANTHROPIC_API_KEY" + auto_approve: "--dangerously-skip-permissions" + structured_output: "--output-format json" + model_flag: "--model" + additional_flags: [] + install_cmd: "npm install -g @anthropic-ai/claude-code" + docs_url: "https://docs.anthropic.com/en/docs/claude-code/cli-usage" + notes: | + Flags verified: + - --print (-p): Query via SDK, then exit + - --dangerously-skip-permissions: Skip permission prompts + - --output-format json: JSON output + - --model: sonnet, opus, or full model name + - --max-turns: Limit agentic turns + - --verbose: Verbose logging + + # ============================================ + # OpenAI Codex CLI + # Docs: https://github.com/openai/codex/blob/main/docs/exec.md + # ============================================ + codex: + name: "OpenAI Codex" + description: "OpenAI's local coding agent" + command: "codex" + subcommand: "exec" # codex exec "task" + task_flag: "" # Positional after exec + auth_env: + - "OPENAI_API_KEY" # Or ChatGPT login + auto_approve: "--full-auto" # Allow file edits + structured_output: "--json" # JSONL streaming output + additional_flags: + - "--sandbox" + - "danger-full-access" # Full access mode + install_cmd: "npm install -g @openai/codex" + docs_url: "https://github.com/openai/codex" + notes: | + Non-interactive mode: codex exec "task" + Flags verified: + - --full-auto: Allow file edits + - --json: Stream JSON events + - --sandbox danger-full-access: Full access + - --output-schema: JSON schema for structured output + - -o/--output-last-message: Output file + + # ============================================ + # Kiro CLI - AWS AI Coding Assistant + # Docs: https://kiro.dev/docs/cli + # ============================================ + kiro: + name: "Kiro" + description: "AWS AI-assisted development CLI" + command: "kiro-cli" + task_flag: "" # Interactive by default, uses chat mode + auth_env: [] # Uses AWS credentials or Kiro login + auto_approve: null # Interactive tool + model_flag: null + additional_flags: + - "--agent" # Specify agent: kiro-cli --agent myagent + install_cmd: "curl -fsSL https://cli.kiro.dev/install | bash" + docs_url: "https://kiro.dev/docs/cli" + notes: | + Kiro is primarily interactive. Key features: + - --agent : Use specific agent + - --resume: Resume conversation + - /editor: Multi-line input + Best for: Spec-driven development, custom agents + + # ============================================ + # Ollama - Free Local LLMs + # Docs: https://github.com/ollama/ollama + # ============================================ + ollama: + name: "Ollama" + description: "Free local LLM execution" + command: "ollama" + subcommand: "run" # ollama run + task_flag: "" # Positional: ollama run codellama "task" + auth_env: [] # No auth needed - fully local + auto_approve: null # No confirmations + model_flag: null # Model is positional: ollama run + default_model: "codellama" + additional_flags: [] + install_cmd: "curl -fsSL https://ollama.com/install.sh | sh" + docs_url: "https://github.com/ollama/ollama" + notes: | + Usage: ollama run "prompt" + Popular coding models: + - codellama: Code-focused Llama + - deepseek-coder: DeepSeek's coding model + - qwen2.5-coder: Alibaba's coding model + - starcoder2: BigCode's model + Zero cost, fully local, no API keys + + # ============================================ + # Goose - Block's AI Coding Agent + # Docs: https://block.github.io/goose/docs/quickstart + # ============================================ + goose: + name: "Goose" + description: "Block's extensible AI coding agent" + command: "goose" + task_flag: "" # Interactive by default + auth_env: [] # Configured via goose configure + auto_approve: null # Interactive + model_flag: null + additional_flags: [] + install_cmd: "curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | bash" + docs_url: "https://block.github.io/goose/docs/quickstart" + notes: | + Goose is primarily interactive desktop/CLI app. + Setup: goose configure + Supports: Tetrate, OpenRouter, and other providers + Features: MCP servers, extensions, recipes + +# ============================================ +# Summary: Non-Interactive Execution +# ============================================ +# +# Tool | Non-Interactive Command +# --------------|-------------------------------------------------- +# aider | aider --message "task" --yes-always --no-git +# claude-code | claude -p "task" --dangerously-skip-permissions +# codex | codex exec "task" --full-auto +# ollama | ollama run codellama "task" +# kiro | (primarily interactive) +# goose | (primarily interactive) +# +# For agentic-fabric integration, we focus on: +# - aider (best non-interactive support) +# - claude-code (excellent --print mode) +# - codex (dedicated exec mode) +# - ollama (simple prompt-response) diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py new file mode 100644 index 0000000..7833197 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py @@ -0,0 +1,328 @@ +"""Universal local CLI runner for single-agent coding tools. + +This runner provides a universal interface for any CLI-based coding agent, +eliminating the need to write custom runners for each tool. Just define the +CLI flags in local_cli_profiles.yaml and it works. + +Supported tools (via profiles): +- Aider: AI pair programming +- Claude Code: Anthropic's coding agent +- OpenAI Codex: OpenAI's local agent +- Ollama: Free local LLMs (codellama, deepseek-coder, etc.) +- Custom: Define your own tool's CLI flags + +Benefits: +- Zero code to add new agents +- Consistent interface across all tools +- Automatic auth env var handling +- Configurable modes (auto-approve, structured output) +""" + +from __future__ import annotations + +import os +import shlex +import subprocess + +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +import yaml + +from agentic_fabric.runners.single_agent_runner import SingleAgentRunner + + +@dataclass +class LocalCLIConfig: + """Configuration for a CLI-based coding agent. + + This defines how to invoke any CLI tool that can execute coding tasks. + All fields map to the tool's command-line interface. + """ + + # Basic command configuration + command: str # Base command (e.g., "aider", "claude", "ollama") + task_flag: str # How to pass task (e.g., "--message", "--print", "" for positional) + + # Optional subcommand (e.g., "run" for "ollama run") + subcommand: str | None = None + + # Authentication + auth_env: list[str] = field(default_factory=list) # Required env vars + + # Mode controls + auto_approve: str | None = None # Auto-approve flag (e.g., "--yes-always") + structured_output: str | None = None # JSON output flag (e.g., "--json") + + # Model configuration + model_flag: str | None = None # How to specify model (e.g., "--model") + default_model: str | None = None # Default model if not specified + + # Working directory + working_dir_flag: str | None = None # Working dir flag (e.g., "--cwd") + + # Additional flags to always include + additional_flags: list[str] = field(default_factory=list) + + # Execution settings + timeout: int = 300 # Timeout in seconds + + # Metadata + name: str = "" + description: str = "" + install_cmd: str = "" + docs_url: str = "" + notes: str = "" + + +class LocalCLIRunner(SingleAgentRunner): + """Universal runner for CLI-based coding agents. + + This runner can invoke any CLI tool that accepts a task/prompt and + executes it. Configuration is done via profiles (built-in or custom). + + Examples: + # Use built-in profile + runner = LocalCLIRunner("aider") + result = runner.run("Add error handling to auth.py") + + # Use custom config + config = LocalCLIConfig( + command="my-tool", + task_flag="--task", + auto_approve="--yes", + ) + runner = LocalCLIRunner(config) + result = runner.run("Fix the bug") + """ + + # Cache for loaded profiles + _profiles_cache: dict[str, LocalCLIConfig] | None = None + + def __init__( + self, + profile: str | LocalCLIConfig | dict[str, Any], + model: str | None = None, + ): + """Initialize the runner with a profile or custom config. + + Args: + profile: Profile name (e.g., "aider"), LocalCLIConfig object, + or dict of config parameters. + model: Optional model override (if tool supports model selection). + """ + if isinstance(profile, str): + # Load from built-in profiles + profiles = self._load_profiles() + if profile not in profiles: + available = list(profiles.keys()) + raise ValueError( + f"Unknown profile '{profile}'. Available: {available}\n" + f"Or provide a LocalCLIConfig object for custom tools." + ) + self.config = profiles[profile] + elif isinstance(profile, dict): + # Convert dict to LocalCLIConfig + self.config = LocalCLIConfig(**profile) + else: + # Use provided LocalCLIConfig directly + self.config = profile + + self.model = model + self.runner_name = f"local_cli_{self.config.command}" + + @classmethod + def _load_profiles(cls) -> dict[str, LocalCLIConfig]: + """Load CLI profiles from local_cli_profiles.yaml. + + Returns: + Dict mapping profile names to LocalCLIConfig objects. + """ + if cls._profiles_cache is not None: + return cls._profiles_cache + + # Find the profiles file + profiles_file = Path(__file__).parent / "local_cli_profiles.yaml" + if not profiles_file.exists(): + raise FileNotFoundError( + f"Profiles file not found: {profiles_file}\nExpected local_cli_profiles.yaml in runners directory." + ) + + # Load and parse YAML + with open(profiles_file) as f: + data = yaml.safe_load(f) + + profiles = {} + for name, config_dict in data.get("profiles", {}).items(): + profiles[name] = LocalCLIConfig(**config_dict) + + cls._profiles_cache = profiles + return profiles + + @classmethod + def get_available_profiles(cls) -> list[str]: + """Get list of available profile names. + + Returns: + List of profile names that can be used. + """ + return list(cls._load_profiles().keys()) + + def is_available(self) -> bool: + """Check if the CLI tool is available (installed and accessible). + + Returns: + True if the command exists in PATH. + """ + from shutil import which + + # Check if command is in PATH + command = self.config.command.split()[0] + return which(command) is not None + + def get_required_env_vars(self) -> list[str]: + """Get list of required environment variables. + + Returns: + List of environment variable names from config.auth_env. + """ + return self.config.auth_env + + def run( + self, + task: str, + working_dir: str | None = None, + auto_approve: bool = True, + structured_output: bool = False, + model: str | None = None, + **kwargs: Any, + ) -> str: + """Execute a task using the configured CLI tool. + + Args: + task: The task to execute (e.g., "Add error handling"). + working_dir: Optional working directory for execution. + auto_approve: Whether to auto-approve changes (if supported). + structured_output: Whether to request JSON output (if supported). + model: Optional model override. + **kwargs: Additional tool-specific arguments. + + Returns: + Tool output as a string. + + Raises: + RuntimeError: If tool execution fails or required env vars missing. + subprocess.TimeoutExpired: If execution exceeds timeout. + """ + # Accept but don't use kwargs (reserved for future extensibility) + _ = kwargs + + # Check required environment variables + missing_vars = [] + for var in self.config.auth_env: + if var not in os.environ: + missing_vars.append(var) + + if missing_vars: + raise RuntimeError( + f"Missing required environment variables for {self.config.command}: " + f"{', '.join(missing_vars)}\n" + f"Set these before running: {', '.join(missing_vars)}" + ) + + # Build command + cmd = self._build_command( + task=task, + working_dir=working_dir, + auto_approve=auto_approve, + structured_output=structured_output, + model=model or self.model, + ) + + # Execute + try: + result = subprocess.run( + cmd, + cwd=working_dir, + capture_output=True, + text=True, + timeout=self.config.timeout, + env=os.environ.copy(), # Pass through environment + check=True, + ) + + return result.stdout + + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"Command failed with exit code {e.returncode}\n" + f"Command: {' '.join(e.cmd)}\n" + f"stdout: {e.stdout}\n" + f"stderr: {e.stderr}" + ) from e + except subprocess.TimeoutExpired as e: + raise RuntimeError(f"Command timed out after {self.config.timeout}s\nCommand: {' '.join(cmd)}") from e + + def _build_command( + self, + task: str, + working_dir: str | None, + auto_approve: bool, + structured_output: bool, + model: str | None, + ) -> list[str]: + """Build the command array for subprocess.run. + + Args: + task: The task to execute. + working_dir: Optional working directory. + auto_approve: Whether to include auto-approve flag. + structured_output: Whether to include structured output flag. + model: Optional model to use. + + Returns: + Command as list of strings. + """ + # Start with base command + cmd = shlex.split(self.config.command) + + # Add subcommand if present (e.g., "ollama run") + if self.config.subcommand: + cmd.append(self.config.subcommand) + + # Add model if specified and tool supports it + # For positional model (like ollama), add before task + if model: + if self.config.model_flag: + cmd.extend([self.config.model_flag, model]) + else: + # Positional model (e.g., "ollama run ") + cmd.append(model) + elif self.config.default_model and not self.config.model_flag: + # Use default model for positional case + cmd.append(self.config.default_model) + + # Add task + if self.config.task_flag: + cmd.extend([self.config.task_flag, task]) + else: + # Positional argument + cmd.append(task) + + # Add auto-approve flag if requested and supported + if auto_approve and self.config.auto_approve: + cmd.append(self.config.auto_approve) + + # Add structured output flag if requested and supported + if structured_output and self.config.structured_output: + cmd.append(self.config.structured_output) + + # Add working directory if supported + if working_dir and self.config.working_dir_flag: + cmd.extend([self.config.working_dir_flag, working_dir]) + + # Add any additional flags + cmd.extend(self.config.additional_flags) + + return cmd diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/registry.py b/packages/agentic-fabric/src/agentic_fabric/runners/registry.py new file mode 100644 index 0000000..a8b031e --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/runners/registry.py @@ -0,0 +1,165 @@ +"""Lazy runtime registry for optional agent frameworks.""" + +from __future__ import annotations + +import importlib + +from dataclasses import dataclass +from types import MappingProxyType +from typing import Any + + +@dataclass(frozen=True) +class RuntimeSpec: + """Metadata required to discover and instantiate one runtime.""" + + name: str + import_name: str + runner_module: str + runner_class: str + install: str + extra: str + description: str = "" + + def as_dict(self, *, available: bool) -> dict[str, Any]: + """Return serializable runtime metadata.""" + return { + "name": self.name, + "import_name": self.import_name, + "runner": f"{self.runner_module}:{self.runner_class}", + "install": self.install, + "extra": self.extra, + "available": available, + "description": self.description, + } + + +class RuntimeUnavailableError(RuntimeError): + """Raised when an optional runtime cannot be selected.""" + + def __init__(self, runtime: str, install: str) -> None: + """Initialize the error with runtime-specific install guidance.""" + super().__init__(f"Runtime '{runtime}' is not available. Install with: {install}") + self.runtime = runtime + self.install = install + + +_RUNTIME_SPECS: dict[str, RuntimeSpec] = {} +_AVAILABILITY_CACHE: dict[str, bool] = {} + + +def register_runtime(spec: RuntimeSpec) -> None: + """Register or replace one runtime spec.""" + _RUNTIME_SPECS[spec.name] = spec + _AVAILABILITY_CACHE.pop(spec.name, None) + + +def runtime_specs() -> MappingProxyType[str, RuntimeSpec]: + """Return registered runtime specs in priority order.""" + return MappingProxyType(_RUNTIME_SPECS) + + +def runtime_names() -> list[str]: + """Return registered runtime names in priority order.""" + return list(_RUNTIME_SPECS) + + +def get_runtime_spec(runtime: str) -> RuntimeSpec: + """Return a registered runtime spec.""" + try: + return _RUNTIME_SPECS[runtime] + except KeyError as exc: + options = ", ".join(runtime_names()) + msg = f"Unknown runtime: {runtime}. Options: [{options}]" + raise ValueError(msg) from exc + + +def clear_runtime_cache() -> None: + """Clear cached optional-runtime availability.""" + _AVAILABILITY_CACHE.clear() + + +def is_runtime_available(runtime: str) -> bool: + """Return whether a runtime import target is importable.""" + if runtime in _AVAILABILITY_CACHE: + return _AVAILABILITY_CACHE[runtime] + if runtime not in _RUNTIME_SPECS: + _AVAILABILITY_CACHE[runtime] = False + return False + + try: + importlib.import_module(_RUNTIME_SPECS[runtime].import_name) + except ImportError: + _AVAILABILITY_CACHE[runtime] = False + else: + _AVAILABILITY_CACHE[runtime] = True + return _AVAILABILITY_CACHE[runtime] + + +def available_runtimes() -> list[str]: + """Return available runtime names in priority order.""" + return [runtime for runtime in runtime_names() if is_runtime_available(runtime)] + + +def runtime_info(runtime: str | None = None) -> list[dict[str, Any]] | dict[str, Any]: + """Return runtime metadata with current availability.""" + if runtime is not None: + spec = get_runtime_spec(runtime) + return spec.as_dict(available=is_runtime_available(runtime)) + return [spec.as_dict(available=is_runtime_available(spec.name)) for spec in _RUNTIME_SPECS.values()] + + +def require_runtime(runtime: str) -> RuntimeSpec: + """Return a runtime spec or raise install guidance if unavailable.""" + spec = get_runtime_spec(runtime) + if not is_runtime_available(runtime): + raise RuntimeUnavailableError(runtime, spec.install) + return spec + + +def load_runner(runtime: str) -> Any: + """Instantiate a runner from the lazy registry.""" + spec = require_runtime(runtime) + module = importlib.import_module(spec.runner_module) + runner_cls = getattr(module, spec.runner_class) + return runner_cls() + + +def install_command(runtime: str) -> str: + """Return the install command for one registered runtime.""" + return get_runtime_spec(runtime).install + + +register_runtime( + RuntimeSpec( + name="crewai", + import_name="crewai", + runner_module="agentic_fabric.runners.crewai_runner", + runner_class="CrewAIRunner", + install='pip install "agentic-fabric[crewai]"', + extra="crewai", + description="Full multi-agent crew orchestration with CrewAI.", + ) +) +register_runtime( + RuntimeSpec( + name="langgraph", + import_name="langgraph", + runner_module="agentic_fabric.runners.langgraph_runner", + runner_class="LangGraphRunner", + install='pip install "agentic-fabric[langgraph]"', + extra="langgraph", + description="LangGraph ReAct and graph workflows.", + ) +) +register_runtime( + RuntimeSpec( + name="strands", + import_name="strands", + runner_module="agentic_fabric.runners.strands_runner", + runner_class="StrandsRunner", + install='pip install "agentic-fabric[strands]"', + extra="strands", + description="AWS Strands single-agent workflow execution.", + ) +) diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/single_agent_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/single_agent_runner.py new file mode 100644 index 0000000..0566683 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/runners/single_agent_runner.py @@ -0,0 +1,75 @@ +"""Single-agent runner base class. + +Single-agent runners execute simple, sequential tasks without multi-agent +collaboration overhead. They're ideal for: +- Quick file edits +- Code generation +- Local development +- Sequential workflows + +Unlike multi-agent crews, single-agent runners: +- Take a single task string as input +- Execute in one pass without delegation +- Return results directly +- Have minimal configuration overhead +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Any + +from agentic_fabric.capabilities import AgentCapabilityProviderMixin, runtime_capability + + +class SingleAgentRunner(AgentCapabilityProviderMixin, ABC): + """Base class for single-agent runners. + + Single-agent runners provide a simpler execution model than multi-agent + crews. They execute a single task and return the result, without the + complexity of agent collaboration, delegation, or hierarchical processes. + """ + + runner_name: str = "single_agent" + + @runtime_capability("run", description="Execute one single-agent task.") + @abstractmethod + def run( + self, + task: str, + working_dir: str | None = None, + **kwargs: Any, + ) -> str: + """Execute a single task and return the result. + + Args: + task: The task to execute (e.g., "Add error handling to auth.py"). + working_dir: Optional working directory for execution. + **kwargs: Additional runner-specific parameters. + + Returns: + Task output as a string. + + Raises: + RuntimeError: If execution fails. + """ + + @runtime_capability("is_available", description="Report whether this local runner can execute.") + def is_available(self) -> bool: + """Check if this runner is available (dependencies installed, etc.). + + Returns: + True if the runner can be used. + """ + # Default implementation - subclasses can override + return True + + @runtime_capability("required_env_vars", description="List environment variables required by this runner.") + def get_required_env_vars(self) -> list[str]: + """Get list of required environment variables. + + Returns: + List of environment variable names required by this runner. + """ + # Default implementation - subclasses can override + return [] diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/strands_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/strands_runner.py new file mode 100644 index 0000000..5312dc0 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/runners/strands_runner.py @@ -0,0 +1,191 @@ +"""AWS Strands runner implementation. + +Strands is lightweight and AWS-native, good for: +- Simple agent workflows +- AWS Bedrock integration +- Minimal dependencies +- Plain Python function tools +""" + +from __future__ import annotations + +from typing import Any + +from agentic_fabric.runners.base import BaseRunner +from agentic_fabric.tools.adapters import resolve_strands_tools + + +class StrandsRunner(BaseRunner): + """Runner that uses AWS Strands for crew execution.""" + + framework_name = "strands" + + def __init__(self): + """Initialize Strands runner.""" + try: + import strands # noqa: F401 + except ImportError as e: + raise RuntimeError("Strands not installed. Install with: pip install strands-agents") from e + + def build_crew(self, crew_config: dict[str, Any]) -> Any: + """Build a Strands agent from configuration. + + Strands uses a single-agent model, so we combine crew tasks + into a system prompt for one capable agent. + + Args: + crew_config: Universal crew configuration. + + Returns: + Strands Agent object. + """ + from strands import Agent + + # Build system prompt from crew description and agent backstories + system_prompt = self._build_system_prompt(crew_config) + + # Collect tools from tasks + tools = self._collect_tools(crew_config) + + # Get LLM/model configuration from crew config + llm_config = crew_config.get("llm", {}) + model_provider = self._get_model_provider(llm_config) + + agent_kwargs = { + "system_prompt": system_prompt, + "tools": tools, + } + + # Add model provider if configured + if model_provider: + agent_kwargs["model_id"] = model_provider + + return Agent(**agent_kwargs) + + def _get_model_provider(self, llm_config: dict | str | None) -> str | None: + """Get Strands-compatible model provider from LLM config. + + Args: + llm_config: LLM configuration from crew config. + + Returns: + Model provider string for Strands, or None for default. + """ + if not llm_config: + return None + + if isinstance(llm_config, str): + return llm_config + + # Extract model from config dict + return llm_config.get("model") + + def run(self, crew: Any, inputs: dict[str, Any]) -> str: + """Execute the Strands agent. + + Args: + crew: Strands Agent object. + inputs: Inputs for the agent. + + Returns: + Agent output as string. + """ + # Convert inputs to prompt + prompt = inputs.get("input", inputs.get("task", str(inputs))) + + result = crew(prompt) + + return str(result) + + def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: + """Build a Strands agent. + + Args: + agent_config: Agent configuration. + tools: Optional tools. + + Returns: + Strands Agent object. + """ + from strands import Agent + + system_prompt = ( + f"You are a {agent_config.get('role', 'helpful assistant')}.\n\n" + f"Goal: {agent_config.get('goal', '')}\n\n" + f"Background: {agent_config.get('backstory', '')}" + ) + + return Agent( + system_prompt=system_prompt, + tools=tools or [], + ) + + def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: + """Build a task for Strands. + + In Strands, tasks are prompts to the agent. Returns a dict + that can be used to construct prompts. + + Args: + task_config: Task configuration. + agent: Agent for the task. + + Returns: + Task configuration dict. + """ + return { + "description": task_config.get("description", ""), + "expected_output": task_config.get("expected_output", ""), + "agent": agent, + } + + def _build_system_prompt(self, crew_config: dict[str, Any]) -> str: + """Build a comprehensive system prompt from crew config. + + Combines all agent roles, goals, and backstories into a single + capable system prompt. + + Args: + crew_config: Crew configuration. + + Returns: + System prompt string. + """ + parts = [] + + # Add crew description + if crew_config.get("description"): + parts.append(f"# Your Purpose\n{crew_config['description']}") + + # Add agent capabilities + agents = crew_config.get("agents", {}) + if agents: + parts.append("\n# Your Capabilities") + for agent_name, agent_cfg in agents.items(): + role = agent_cfg.get("role", agent_name) + goal = agent_cfg.get("goal", "") + parts.append(f"\n## {role}") + if goal: + parts.append(f"Goal: {goal}") + + # Add task context + tasks = crew_config.get("tasks", {}) + if tasks: + parts.append("\n# Tasks You Can Perform") + for task_name, task_cfg in tasks.items(): + desc = task_cfg.get("description", "") + if desc: + # Only add ellipsis if actually truncated + if len(desc) > 200: + parts.append(f"\n- {task_name}: {desc[:200]}...") + else: + parts.append(f"\n- {task_name}: {desc}") + + return "\n".join(parts) + + def _collect_tools(self, crew_config: dict[str, Any]) -> list: + """Collect tools declared by agents in crew configuration.""" + tool_names: list[str] = [] + for agent_cfg in crew_config.get("agents", {}).values(): + tool_names.extend(agent_cfg.get("tools", [])) + return resolve_strands_tools(tool_names) diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/__init__.py b/packages/agentic-fabric/src/agentic_fabric/tools/__init__.py new file mode 100644 index 0000000..86fd871 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/tools/__init__.py @@ -0,0 +1,75 @@ +"""Custom tools for agentic-fabric. + +Tool modules are imported lazily so the core package can be imported without +pulling in optional framework dependencies like CrewAI or scraping extras. +""" + +from __future__ import annotations + +from contextlib import suppress +from importlib import import_module +from typing import TYPE_CHECKING, Any + + +if TYPE_CHECKING: + from agentic_fabric.tools.file_tools import DirectoryListTool, GameCodeReaderTool, GameCodeWriterTool + from agentic_fabric.tools.scraping_tools import CrawlWebsiteTool, ScrapeWebsiteTool + + +def _load_attr(module_name: str, attr_name: str) -> Any: + module = import_module(module_name) + return getattr(module, attr_name) + + +def __getattr__(name: str) -> Any: + if name in {"DirectoryListTool", "GameCodeReaderTool", "GameCodeWriterTool"}: + return _load_attr("agentic_fabric.tools.file_tools", name) + if name in {"CrawlWebsiteTool", "ScrapeWebsiteTool"}: + return _load_attr("agentic_fabric.tools.scraping_tools", name) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +def get_file_tools() -> list[Any]: + """Get the standard file manipulation tools.""" + return [ + __getattr__("GameCodeReaderTool")(), + __getattr__("GameCodeWriterTool")(), + __getattr__("DirectoryListTool")(), + ] + + +def get_scraping_tools() -> list[Any]: + """Get the web scraping tools.""" + return [ + __getattr__("ScrapeWebsiteTool")(), + __getattr__("CrawlWebsiteTool")(), + ] + + +def get_all_tools() -> list[Any]: + """Get all available tools.""" + tools = get_file_tools() + + with suppress(ImportError): + tools.extend(get_scraping_tools()) + + try: + from mesh_toolkit.agent_tools.crewai import get_tools as get_meshy_tools + + tools.extend(get_meshy_tools()) + except ImportError: + pass + + return tools + + +__all__ = [ + "CrawlWebsiteTool", + "DirectoryListTool", + "GameCodeReaderTool", + "GameCodeWriterTool", + "ScrapeWebsiteTool", + "get_all_tools", + "get_file_tools", + "get_scraping_tools", +] diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/adapters.py b/packages/agentic-fabric/src/agentic_fabric/tools/adapters.py new file mode 100644 index 0000000..31ef1a1 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/tools/adapters.py @@ -0,0 +1,123 @@ +"""Framework-specific adapters for configured tools.""" + +from __future__ import annotations + +import inspect +import logging +import re + +from typing import Any + +from agentic_fabric.tools.registry import resolve_tools + + +logger = logging.getLogger(__name__) + + +def _tool_name(tool_obj: Any) -> str: + """Build a framework-safe tool name.""" + raw_name = getattr(tool_obj, "name", None) or getattr(tool_obj, "__name__", None) or tool_obj.__class__.__name__ + cleaned = re.sub(r"[^0-9A-Za-z_]+", "_", str(raw_name)).strip("_") or "tool" + if cleaned[0].isdigit(): + cleaned = f"tool_{cleaned}" + return cleaned + + +def _tool_description(tool_obj: Any) -> str: + """Get the best available description for a tool object.""" + return str( + getattr(tool_obj, "description", None) + or inspect.getdoc(tool_obj) + or f"Execute the {_tool_name(tool_obj)} tool." + ) + + +def _invoke_tool(tool_obj: Any, kwargs: dict[str, Any]) -> Any: + """Execute a configured tool object.""" + if hasattr(tool_obj, "_run"): + return tool_obj._run(**kwargs) + if callable(tool_obj): + return tool_obj(**kwargs) + raise TypeError(f"Tool {tool_obj!r} is not callable") + + +def _build_runner(tool_obj: Any, name: str, description: str) -> Any: + """Create a plain callable wrapper for a configured tool.""" + + def runner(**kwargs: Any) -> Any: + return _invoke_tool(tool_obj, kwargs) + + runner.__name__ = name + runner.__doc__ = description + return runner + + +def resolve_langgraph_tools(tool_names: list[str]) -> list[Any]: + """Resolve configured tool names to LangGraph-compatible tools.""" + if not tool_names: + return [] + + resolved_tools = resolve_tools(tool_names) + if not resolved_tools: + return [] + + try: + from langchain_core.tools import StructuredTool + except ImportError as exc: + logger.warning("LangGraph tool adaptation unavailable; skipping configured tools: %s", exc) + return [] + + adapted_tools: list[Any] = [] + for tool_obj in resolved_tools: + if hasattr(tool_obj, "invoke") and hasattr(tool_obj, "name"): + adapted_tools.append(tool_obj) + continue + + name = _tool_name(tool_obj) + description = _tool_description(tool_obj) + args_schema = getattr(tool_obj, "args_schema", None) + runner = _build_runner(tool_obj, name, description) + + adapted_tools.append( + StructuredTool.from_function( + func=runner, + name=name, + description=description, + args_schema=args_schema, + infer_schema=args_schema is None, + ) + ) + + return adapted_tools + + +def resolve_strands_tools(tool_names: list[str]) -> list[Any]: + """Resolve configured tool names to Strands-compatible tools.""" + if not tool_names: + return [] + + resolved_tools = resolve_tools(tool_names) + if not resolved_tools: + return [] + + try: + from strands import tool as strands_tool + except ImportError as exc: + logger.warning("Strands tool adaptation unavailable; skipping configured tools: %s", exc) + return [] + + adapted_tools: list[Any] = [] + for tool_obj in resolved_tools: + if hasattr(tool_obj, "TOOL_SPEC"): + adapted_tools.append(tool_obj) + continue + + name = _tool_name(tool_obj) + description = _tool_description(tool_obj) + args_schema = getattr(tool_obj, "args_schema", None) + input_schema = args_schema.model_json_schema() if args_schema is not None else None + runner = _build_runner(tool_obj, name, description) + + adapted_tools.append(strands_tool(name=name, description=description, inputSchema=input_schema)(runner)) + + return adapted_tools diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py b/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py new file mode 100644 index 0000000..de55664 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py @@ -0,0 +1,278 @@ +"""File manipulation tools for CrewAI agents. + +These tools enable agents to read and write code to specific directories +in game package codebases (e.g., packages/otterfall). +""" + +from __future__ import annotations + +import os + +from pathlib import Path + +from crewai.tools import BaseTool +from pydantic import BaseModel, Field + + +def _find_workspace_root() -> Path | None: + """Search upward for workspace root (contains pyproject.toml with workspace).""" + current = Path(__file__).resolve().parent + for parent in [current, *list(current.parents)]: + pyproject = parent / "pyproject.toml" + if pyproject.exists(): + # Check if this is the workspace root (has packages/ directory) + packages_dir = parent / "packages" + if packages_dir.exists() and packages_dir.is_dir(): + return parent + return None + + +def get_workspace_root(package_name: str | None = None) -> Path: + """Get the workspace root directory for the target game code package. + + Returns packages/ as the workspace root, where the game code lives. + + Uses marker file search to find workspace root reliably, regardless of + where this module is installed or imported from. + + Args: + package_name: Name of the target package. If not provided, + uses TARGET_PACKAGE environment variable, or defaults to "otterfall". + + Returns: + Path to packages/ directory. + """ + # Determine the target package name + if package_name is None: + package_name = os.environ.get("TARGET_PACKAGE", "otterfall") + + # Find workspace root using marker file search + workspace_root = _find_workspace_root() + if workspace_root: + target_dir = workspace_root / "packages" / package_name + if target_dir.exists(): + return target_dir + + # Fallback: try environment variable for root directory + env_root_var = f"{package_name.upper()}_ROOT" + if env_root_var in os.environ: + return Path(os.environ[env_root_var]).resolve() + + # Last fallback - current directory (shouldn't happen in normal use) + return Path.cwd() + + +# Allowed directories for writing (relative to packages/otterfall) +ALLOWED_WRITE_DIRS = [ + "src/ecs", # ECS components, world, data + "src/ecs/data", # Species definitions, etc. + "src/ecs/systems", # ECS systems + "src/components", # React/R3F components + "src/components/ui", # UI components + "src/stores", # Zustand stores + "src/systems", # Non-ECS systems + "src/utils", # Utility functions + "src/types", # TypeScript types +] + +# Allowed file extensions +ALLOWED_EXTENSIONS = {".ts", ".tsx", ".json", ".md"} + + +class WriteFileInput(BaseModel): + """Input schema for GameCodeWriterTool.""" + + file_path: str = Field(description="Relative path from workspace root (e.g., 'src/ecs/data/NewComponent.ts')") + content: str = Field(description="The TypeScript/TSX code content to write") + + +class GameCodeWriterTool(BaseTool): + """Tool for writing code files to a game package codebase. + + This tool is restricted to specific directories to ensure agents + only modify appropriate parts of the codebase. + """ + + name: str = "Write Game Code File" + description: str = """ + Write a code file to the target game codebase (e.g., packages/). + + The target package is configurable via TARGET_PACKAGE environment variable. + + ALLOWED DIRECTORIES: + - src/ecs - ECS components, world definition + - src/ecs/data - Species data, biome configs + - src/ecs/systems - ECS systems + - src/components - React Three Fiber components + - src/components/ui - UI components (menus, HUD) + - src/stores - Zustand state stores + - src/systems - Non-ECS game systems + - src/utils - Utility functions + - src/types - TypeScript type definitions + + ALLOWED EXTENSIONS: .ts, .tsx, .json, .md + + Example: + file_path: "src/ecs/data/species.ts" + content: "export const PREDATOR_SPECIES = { ... }" + """ + args_schema: type[BaseModel] = WriteFileInput + + def _run(self, file_path: str, content: str) -> str: + """Write the file content to the specified path.""" + try: + # Validate path + clean_path = file_path.strip().replace("\\", "/") + + # Check for path traversal + if ".." in clean_path or clean_path.startswith("/"): + return f"Error: Invalid path '{clean_path}'. Path traversal not allowed." + + # Check allowed directories + is_allowed = any(clean_path.startswith(allowed_dir) for allowed_dir in ALLOWED_WRITE_DIRS) + if not is_allowed: + return f"Error: Path '{clean_path}' is not in an allowed directory. Allowed: {ALLOWED_WRITE_DIRS}" + + # Check extension + ext = Path(clean_path).suffix.lower() + if ext not in ALLOWED_EXTENSIONS: + return f"Error: Extension '{ext}' not allowed. Allowed: {ALLOWED_EXTENSIONS}" + + # Construct full path + workspace_root = get_workspace_root() + full_path = workspace_root / clean_path + + # Create parent directories + full_path.parent.mkdir(parents=True, exist_ok=True) + + # Write content + with open(full_path, "w", encoding="utf-8") as f: + f.write(content) + + return f"Successfully wrote {len(content)} bytes to {clean_path}" + + except PermissionError: + return f"Error: Permission denied writing to {file_path}" + except Exception as e: + return f"Error writing file: {e!s}" + + +class ReadFileInput(BaseModel): + """Input schema for GameCodeReaderTool.""" + + file_path: str = Field(description="Relative path from workspace root (e.g., 'src/ecs/components.ts')") + + +class GameCodeReaderTool(BaseTool): + """Tool for reading code files from a game package codebase. + + Use this to understand existing patterns before writing new code. + """ + + name: str = "Read Game Code File" + description: str = """ + Read a code file from the target package's codebase. + + The target package is determined by the TARGET_PACKAGE environment variable. + + Use this tool to: + - Understand existing patterns + - See how similar components are structured + - Check imports and dependencies + + Example: + file_path: "src/ecs/components.ts" + """ + args_schema: type[BaseModel] = ReadFileInput + + def _run(self, file_path: str) -> str: + """Read the file content from the specified path.""" + try: + clean_path = file_path.strip().replace("\\", "/") + + if ".." in clean_path: + return f"Error: Path traversal not allowed in '{clean_path}'" + + workspace_root = get_workspace_root() + full_path = workspace_root / clean_path + + if not full_path.exists(): + return f"Error: File not found: {clean_path}" + + if not full_path.is_file(): + return f"Error: Path is not a file: {clean_path}" + + # Limit file size + if full_path.stat().st_size > 100_000: # 100KB limit + return f"Error: File too large (>{100_000} bytes)" + + with open(full_path, encoding="utf-8") as f: + return f.read() + + except PermissionError: + return f"Error: Permission denied reading {file_path}" + except Exception as e: + return f"Error reading file: {e!s}" + + +class ListDirInput(BaseModel): + """Input schema for DirectoryListTool.""" + + directory: str = Field(description="Relative directory path from workspace root (e.g., 'src/ecs')") + + +class DirectoryListTool(BaseTool): + """Tool for listing files in a directory. + + Use this to discover existing files and understand project structure. + """ + + name: str = "List Directory Contents" + description: str = """ + List files and subdirectories in the target package codebase. + + The target package is determined by the TARGET_PACKAGE environment variable. + + Use this to: + - Discover existing components + - Understand project structure + - Find files to read or reference + + Example: + directory: "src/ecs/data" + """ + args_schema: type[BaseModel] = ListDirInput + + def _run(self, directory: str) -> str: + """List directory contents.""" + try: + clean_path = directory.strip().replace("\\", "/") + + if ".." in clean_path: + return "Error: Path traversal not allowed" + + workspace_root = get_workspace_root() + full_path = workspace_root / clean_path + + if not full_path.exists(): + return f"Error: Directory not found: {clean_path}" + + if not full_path.is_dir(): + return f"Error: Path is not a directory: {clean_path}" + + entries = [] + for entry in sorted(full_path.iterdir()): + if entry.name.startswith("."): + continue + prefix = "📁" if entry.is_dir() else "📄" + entries.append(f"{prefix} {entry.name}") + + if not entries: + return f"Directory {clean_path} is empty" + + return f"Contents of {clean_path}:\n" + "\n".join(entries) + + except PermissionError: + return f"Error: Permission denied accessing {directory}" + except Exception as e: + return f"Error listing directory: {e!s}" diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/registry.py b/packages/agentic-fabric/src/agentic_fabric/tools/registry.py new file mode 100644 index 0000000..c6ed1f5 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/tools/registry.py @@ -0,0 +1,137 @@ +"""Tool resolution helpers for configured crew tools. + +This module turns YAML-declared tool names into instantiated tool objects +without forcing every optional dependency to import at module load time. +""" + +from __future__ import annotations + +import importlib +import logging + +from collections.abc import Callable +from typing import Any + + +logger = logging.getLogger(__name__) + +ToolFactory = Callable[[], Any] + + +def _build_factory(module_name: str, attr_name: str) -> ToolFactory: + """Create a lazy factory for a tool class or callable.""" + + def factory() -> Any: + module = importlib.import_module(module_name) + tool = getattr(module, attr_name) + return tool() if callable(tool) else tool + + return factory + + +_TOOL_FACTORIES: dict[str, ToolFactory] = { + "GameCodeWriterTool": _build_factory("agentic_fabric.tools.file_tools", "GameCodeWriterTool"), + "GameCodeReaderTool": _build_factory("agentic_fabric.tools.file_tools", "GameCodeReaderTool"), + "DirectoryListTool": _build_factory("agentic_fabric.tools.file_tools", "DirectoryListTool"), + "ScrapeWebsiteTool": _build_factory("crewai_tools", "ScrapeWebsiteTool"), + "CrawlWebsiteTool": _build_factory("agentic_fabric.tools.scraping_tools", "CrawlWebsiteTool"), +} + +_TOOL_ALIASES = { + "FileWriteTool": "GameCodeWriterTool", + "WriteFileTool": "GameCodeWriterTool", + "FileReadTool": "GameCodeReaderTool", + "ReadFileTool": "GameCodeReaderTool", + "ListDirectoryTool": "DirectoryListTool", + "ListFilesTool": "DirectoryListTool", + "mcp://filesystem/write_file": "GameCodeWriterTool", + "mcp://filesystem/read_file": "GameCodeReaderTool", + "mcp://filesystem/list_directory": "DirectoryListTool", +} + + +def _canonical_tool_name(tool_name: str) -> str: + """Normalize aliases to a canonical tool identifier.""" + return _TOOL_ALIASES.get(tool_name, tool_name) + + +def _resolve_vendor_tool(tool_name: str) -> Any | None: + """Resolve vendor-backed tool references without importing vendor SDKs.""" + if tool_name.startswith("vendor://"): + path = tool_name.removeprefix("vendor://") + provider, separator, operation = path.partition("/") + if provider and separator and operation: + from agentic_fabric.tools.vendor import VendorCapabilityTool + + return VendorCapabilityTool(provider, operation) + + if tool_name.startswith("vendor:"): + _, provider, operation = tool_name.split(":", 2) if tool_name.count(":") >= 2 else ("", "", "") + if provider and operation: + from agentic_fabric.tools.vendor import VendorCapabilityTool + + return VendorCapabilityTool(provider, operation) + + return None + + +def resolve_tool(tool_name: str) -> Any | None: + """Resolve a configured tool name to an instantiated tool object. + + Supports: + - built-in aliases like ``FileWriteTool`` + - selected filesystem MCP URIs + - fully-qualified ``module:attribute`` references + - fully-qualified ``package.module.ClassName`` references + """ + canonical_name = _canonical_tool_name(tool_name) + + vendor_tool = _resolve_vendor_tool(canonical_name) + if vendor_tool is not None: + return vendor_tool + + if canonical_name in _TOOL_FACTORIES: + return _TOOL_FACTORIES[canonical_name]() + + if canonical_name.startswith("mcp://"): + return None + + try: + if ":" in canonical_name: + module_name, attr_name = canonical_name.split(":", 1) + return _build_factory(module_name, attr_name)() + + if "." in canonical_name: + module_name, _, attr_name = canonical_name.rpartition(".") + if module_name and attr_name: + return _build_factory(module_name, attr_name)() + except (ImportError, AttributeError) as exc: + logger.warning("Failed to resolve tool '%s': %s", tool_name, exc) + return None + + return None + + +def resolve_tools(tool_names: list[str]) -> list[Any]: + """Resolve configured tool names to instantiated tool objects. + + Unknown or unavailable tools are skipped with a warning so that crews can + continue to run with the capabilities available in the current environment. + """ + resolved: list[Any] = [] + seen: set[str] = set() + + for tool_name in tool_names: + canonical_name = _canonical_tool_name(tool_name) + if canonical_name in seen: + continue + + tool = resolve_tool(tool_name) + if tool is None: + logger.warning("Skipping unresolved tool '%s'", tool_name) + continue + + resolved.append(tool) + seen.add(canonical_name) + + return resolved diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py b/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py new file mode 100644 index 0000000..ae179a6 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py @@ -0,0 +1,69 @@ +"""Website scraping tools for CrewAI-compatible runners.""" + +from __future__ import annotations + +import logging + +from urllib.parse import urljoin, urlparse + +import requests + +from bs4 import BeautifulSoup +from crewai_tools import ScrapeWebsiteTool + + +log = logging.getLogger(__name__) + + +class CrawlWebsiteTool(ScrapeWebsiteTool): + """A tool for crawling a website and scraping its content. + + This tool extends ScrapeWebsiteTool to support crawling multiple pages + starting from a given URL. + """ + + name: str = "CrawlWebsiteTool" + description: str = "Crawl a website from a given URL and scrape its content." + + def _run(self, url: str) -> str: + """Crawls a website and returns the scraped content. + + Args: + url: The URL to start crawling from. + + Returns: + The scraped content of the website. + """ + scraped_content = "" + visited_urls = set() + urls_to_visit = [url] + base_netloc = urlparse(url).netloc + + while urls_to_visit: + current_url = urls_to_visit.pop(0) + if current_url in visited_urls: + continue + + try: + response = requests.get(current_url, timeout=30) + response.raise_for_status() + visited_urls.add(current_url) + + soup = BeautifulSoup(response.content, "html.parser") + scraped_content += self._scrape_content(soup) + + for link in soup.find_all("a", href=True): + next_url = urljoin(current_url, link["href"]) + if urlparse(next_url).netloc == base_netloc and next_url not in visited_urls: + urls_to_visit.append(next_url) + + except requests.RequestException: + log.exception("Error crawling %s", current_url) + + return scraped_content + + def _scrape_content(self, soup: BeautifulSoup) -> str: + """Scrapes the readable content from a BeautifulSoup object.""" + for script_or_style in soup(["script", "style"]): + script_or_style.decompose() + return " ".join(t.strip() for t in soup.stripped_strings) diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/vendor.py b/packages/agentic-fabric/src/agentic_fabric/tools/vendor.py new file mode 100644 index 0000000..91c431f --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/tools/vendor.py @@ -0,0 +1,37 @@ +"""Vendor-backed agent tool wrappers.""" + +from __future__ import annotations + +from typing import Any + + +class VendorCapabilityTool: + """Generic tool that routes work through AgenticData/VendorData.""" + + def __init__( + self, + provider: str, + operation: str, + *, + data: Any | None = None, + description: str | None = None, + ) -> None: + """Initialize a lazy wrapper for one provider operation.""" + self.provider = provider + self.operation = operation + self.name = f"{provider}_{operation}".replace("-", "_") + self.description = description or f"Run vendor-fabric operation {operation!r} on provider {provider!r}." + self.data = data + + def _run(self, **kwargs: Any) -> Any: + """Execute the vendor operation with keyword arguments.""" + data = self.data + if data is None: + from agentic_fabric.agentic_data import AgenticData + + data = AgenticData() + return data.call(self.operation, self.provider, **kwargs) + + def __call__(self, **kwargs: Any) -> Any: + """Allow framework adapters to treat this as a plain callable.""" + return self._run(**kwargs) diff --git a/packages/agentic-fabric/src/agentic_fabric/utils/__init__.py b/packages/agentic-fabric/src/agentic_fabric/utils/__init__.py new file mode 100644 index 0000000..c649d85 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/utils/__init__.py @@ -0,0 +1,6 @@ +"""Utility helpers for agentic-fabric.""" + +from .files import load_config + + +__all__ = ["load_config"] diff --git a/packages/agentic-fabric/src/agentic_fabric/utils/files.py b/packages/agentic-fabric/src/agentic_fabric/utils/files.py new file mode 100644 index 0000000..dd267de --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/utils/files.py @@ -0,0 +1,22 @@ +"""File loading helpers.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any, cast + +import yaml + + +def load_config(path: Path | str) -> dict[str, Any]: + """Loads a YAML configuration file. + + Args: + path: The path to the YAML file. + + Returns: + A dictionary containing the configuration. + """ + with open(path) as f: + loaded = yaml.safe_load(f) or {} + return cast(dict[str, Any], loaded) diff --git a/packages/agentic-fabric/tests/__init__.py b/packages/agentic-fabric/tests/__init__.py new file mode 100644 index 0000000..5b3f2a0 --- /dev/null +++ b/packages/agentic-fabric/tests/__init__.py @@ -0,0 +1,3 @@ +"""Tests for agentic-fabric package.""" + +from __future__ import annotations diff --git a/packages/agentic-fabric/tests/_crew_mocker.py b/packages/agentic-fabric/tests/_crew_mocker.py new file mode 100644 index 0000000..c1cf5ad --- /dev/null +++ b/packages/agentic-fabric/tests/_crew_mocker.py @@ -0,0 +1,509 @@ +"""Mocking utilities for agentic-fabric testing. + +This module provides specialized mocking fixtures that build on top of pytest-mock +to make it easy to test agentic-fabric components without needing the actual +AI frameworks installed. + +The fixtures automatically mock framework modules and provide convenient methods +for patching agentic-fabric internals. +""" + +from __future__ import annotations + +import sys + +from collections.abc import Generator +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any + +import pytest + + +if TYPE_CHECKING: + from pytest_mock import MockerFixture + + +# ============================================================================= +# Framework Module Lists +# ============================================================================= + +CREWAI_MODULES = [ + "crewai", + "crewai.knowledge", + "crewai.knowledge.source", + "crewai.knowledge.source.text_file_knowledge_source", +] + +LANGGRAPH_MODULES = [ + "langgraph", + "langgraph.prebuilt", + "langchain_anthropic", +] + +STRANDS_MODULES = [ + "strands", +] + +ALL_FRAMEWORK_MODULES = CREWAI_MODULES + LANGGRAPH_MODULES + STRANDS_MODULES + + +# ============================================================================= +# CrewMocker Class - Enhanced mocker for agentic-fabric testing +# ============================================================================= + + +@dataclass +class CrewMocker: + """Enhanced mocker specifically for agentic-fabric testing. + + This class wraps pytest-mock's MockerFixture and provides convenience + methods for mocking AI framework components commonly used in agentic-fabric. + + Attributes: + mocker: The underlying pytest-mock MockerFixture. + mocked_modules: Dictionary of currently mocked framework modules. + + Example: + def test_crewai_runner(crew_mocker): + # Mock CrewAI components + mock_agent = crew_mocker.mock_crewai_agent() + mock_crew = crew_mocker.mock_crewai_crew() + + # Test runner behavior + from agentic_fabric.runners.crewai_runner import CrewAIRunner + runner = CrewAIRunner() + ... + """ + + mocker: MockerFixture + mocked_modules: dict[str, Any] = field(default_factory=dict) + _original_modules: dict[str, Any] = field(default_factory=dict) + + # Expose common pytest-mock attributes + @property + def MagicMock(self) -> Any: + """Access to mocker.MagicMock.""" + return self.mocker.MagicMock + + @property + def Mock(self) -> Any: + """Access to mocker.Mock.""" + return self.mocker.Mock + + @property + def patch(self) -> Any: + """Access to mocker.patch.""" + return self.mocker.patch + + @property + def spy(self) -> Any: + """Access to mocker.spy.""" + return self.mocker.spy + + @property + def stub(self) -> Any: + """Access to mocker.stub.""" + return self.mocker.stub + + def mock_module(self, module_name: str) -> Any: + """Mock a single module in sys.modules. + + Args: + module_name: Full module path (e.g., 'crewai.Agent'). + + Returns: + The mock object for the module. + """ + if module_name in self.mocked_modules: + return self.mocked_modules[module_name] + + mock = self.mocker.MagicMock() + if module_name in sys.modules: + self._original_modules[module_name] = sys.modules[module_name] + sys.modules[module_name] = mock + self.mocked_modules[module_name] = mock + return mock + + def mock_modules(self, module_names: list[str]) -> dict[str, Any]: + """Mock multiple modules in sys.modules. + + Args: + module_names: List of module paths to mock. + + Returns: + Dictionary mapping module names to their mock objects. + """ + return {name: self.mock_module(name) for name in module_names} + + def restore_modules(self) -> None: + """Restore all mocked modules to their original state.""" + for module_name in list(self.mocked_modules.keys()): + if module_name in self._original_modules: + sys.modules[module_name] = self._original_modules[module_name] + else: + sys.modules.pop(module_name, None) + self.mocked_modules.clear() + self._original_modules.clear() + + # ========================================================================= + # CrewAI Mocking Helpers + # ========================================================================= + + def mock_crewai(self) -> dict[str, Any]: + """Mock all CrewAI framework modules. + + Returns: + Dictionary of mocked CrewAI modules. + """ + mocks = self.mock_modules(CREWAI_MODULES) + # Set up nested module attributes + if "crewai.knowledge.source.text_file_knowledge_source" in mocks: + mocks[ + "crewai.knowledge.source.text_file_knowledge_source" + ].TextFileKnowledgeSource = self.mocker.MagicMock() + return mocks + + def mock_crewai_agent(self, **kwargs: Any) -> Any: + """Create a mock for crewai.Agent. + + Args: + **kwargs: Additional attributes to set on the mock. + + Returns: + Mock Agent object. + """ + mock_agent = self.mocker.MagicMock() + for key, value in kwargs.items(): + setattr(mock_agent, key, value) + return mock_agent + + def mock_crewai_task(self, **kwargs: Any) -> Any: + """Create a mock for crewai.Task. + + Args: + **kwargs: Additional attributes to set on the mock. + + Returns: + Mock Task object. + """ + mock_task = self.mocker.MagicMock() + for key, value in kwargs.items(): + setattr(mock_task, key, value) + return mock_task + + def mock_crewai_crew(self, result: str = "Test result", **kwargs: Any) -> Any: + """Create a mock for crewai.Crew with kickoff behavior. + + Args: + result: The result to return from kickoff(). + **kwargs: Additional attributes to set on the mock. + + Returns: + Mock Crew object with kickoff configured. + """ + mock_crew = self.mocker.MagicMock() + mock_result = self.mocker.MagicMock() + mock_result.raw = result + mock_crew.kickoff.return_value = mock_result + for key, value in kwargs.items(): + setattr(mock_crew, key, value) + return mock_crew + + def patch_crewai_agent(self) -> Any: + """Patch crewai.Agent and return the mock. + + Returns: + The mock for crewai.Agent. + """ + return self.mocker.patch("crewai.Agent") + + def patch_crewai_task(self) -> Any: + """Patch crewai.Task and return the mock. + + Returns: + The mock for crewai.Task. + """ + return self.mocker.patch("crewai.Task") + + def patch_crewai_crew(self) -> Any: + """Patch crewai.Crew and return the mock. + + Returns: + The mock for crewai.Crew. + """ + return self.mocker.patch("crewai.Crew") + + def patch_crewai_process(self) -> Any: + """Patch crewai.Process and return the mock. + + Returns: + The mock for crewai.Process. + """ + return self.mocker.patch("crewai.Process") + + def patch_knowledge_source(self) -> Any: + """Get the TextFileKnowledgeSource mock from the mocked crewai module. + + Note: Must call mock_crewai() first. + + Returns: + The mock for TextFileKnowledgeSource. + """ + # If the module is already mocked, return its TextFileKnowledgeSource + if "crewai.knowledge.source.text_file_knowledge_source" in self.mocked_modules: + return self.mocked_modules["crewai.knowledge.source.text_file_knowledge_source"].TextFileKnowledgeSource + # Otherwise, fall back to patching + return self.mocker.patch("crewai.knowledge.source.text_file_knowledge_source.TextFileKnowledgeSource") + + # ========================================================================= + # LangGraph Mocking Helpers + # ========================================================================= + + def mock_langgraph(self) -> dict[str, Any]: + """Mock all LangGraph framework modules. + + Returns: + Dictionary of mocked LangGraph modules. + """ + mocks = self.mock_modules(LANGGRAPH_MODULES) + # Set up the prebuilt module with create_react_agent + if "langgraph.prebuilt" in mocks: + mocks["langgraph.prebuilt"].create_react_agent = self.mocker.MagicMock() + return mocks + + def patch_create_react_agent(self) -> Any: + """Get the create_react_agent mock from the mocked langgraph module. + + Note: Must call mock_langgraph() first. + + Returns: + The mock for create_react_agent. + """ + # If langgraph.prebuilt is already mocked, return its create_react_agent + if "langgraph.prebuilt" in self.mocked_modules: + return self.mocked_modules["langgraph.prebuilt"].create_react_agent + # Otherwise, fall back to patching + return self.mocker.patch("langgraph.prebuilt.create_react_agent") + + def patch_chat_anthropic(self) -> Any: + """Get the ChatAnthropic mock from the mocked langchain_anthropic module. + + Note: Must call mock_langgraph() first. + + Returns: + The mock for ChatAnthropic. + """ + # If langchain_anthropic is already mocked, return its ChatAnthropic + if "langchain_anthropic" in self.mocked_modules: + return self.mocked_modules["langchain_anthropic"].ChatAnthropic + # Otherwise, fall back to patching + return self.mocker.patch("langchain_anthropic.ChatAnthropic") + + def mock_langgraph_graph(self, result: str = "Test response") -> Any: + """Create a mock LangGraph graph with invoke behavior. + + Args: + result: The result content to return from invoke(). + + Returns: + Mock graph object with invoke configured. + """ + mock_graph = self.mocker.MagicMock() + mock_message = self.mocker.MagicMock() + mock_message.content = result + mock_graph.invoke.return_value = {"messages": [mock_message]} + return mock_graph + + # ========================================================================= + # Strands Mocking Helpers + # ========================================================================= + + def mock_strands(self) -> dict[str, Any]: + """Mock all Strands framework modules. + + Returns: + Dictionary of mocked Strands modules. + """ + return self.mock_modules(STRANDS_MODULES) + + def patch_strands_agent(self) -> Any: + """Patch strands.Agent and return the mock. + + Returns: + The mock for strands.Agent. + """ + return self.mocker.patch("strands.Agent") + + def mock_strands_agent(self, result: str = "Test response") -> Any: + """Create a mock Strands agent with call behavior. + + Args: + result: The result to return when agent is called. + + Returns: + Mock agent object. + """ + mock_agent = self.mocker.MagicMock() + mock_agent.return_value = result + return mock_agent + + # ========================================================================= + # All Frameworks + # ========================================================================= + + def mock_all_frameworks(self) -> dict[str, Any]: + """Mock all AI framework modules (CrewAI, LangGraph, Strands). + + Returns: + Dictionary of all mocked framework modules. + """ + return self.mock_modules(ALL_FRAMEWORK_MODULES) + + # ========================================================================= + # agentic-fabric Internal Mocking + # ========================================================================= + + def patch_get_llm(self, return_value: Any = None) -> Any: + """Patch agentic_fabric.config.llm.get_llm. + + Args: + return_value: Value to return from get_llm. If None, creates a MagicMock. + + Returns: + The mock for get_llm. + """ + mock = self.mocker.patch("agentic_fabric.config.llm.get_llm") + mock.return_value = return_value or self.mocker.MagicMock() + return mock + + def patch_discover_packages(self, packages: dict[str, Any] | None = None) -> Any: + """Patch agentic_fabric.core.discovery.discover_packages. + + Args: + packages: Dictionary of packages to return. If None, returns empty dict. + + Returns: + The mock for discover_packages. + """ + mock = self.mocker.patch("agentic_fabric.core.discovery.discover_packages") + mock.return_value = packages or {} + return mock + + def patch_get_crew_config(self, config: dict[str, Any] | None = None) -> Any: + """Patch agentic_fabric.core.discovery.get_crew_config. + + Args: + config: Config to return. If None, returns minimal valid config. + + Returns: + The mock for get_crew_config. + """ + mock = self.mocker.patch("agentic_fabric.core.discovery.get_crew_config") + mock.return_value = config or {"name": "test", "agents": {}, "tasks": {}} + return mock + + def patch_run_crew_auto(self, result: str = "Test result") -> Any: + """Patch agentic_fabric.core.decomposer.run_crew_auto. + + Args: + result: Result string to return from run_crew_auto. + + Returns: + The mock for run_crew_auto. + """ + mock = self.mocker.patch("agentic_fabric.core.decomposer.run_crew_auto") + mock.return_value = result + return mock + + +# ============================================================================= +# Fixtures +# ============================================================================= + + +@pytest.fixture +def crew_mocker(mocker: MockerFixture) -> Generator[CrewMocker, None, None]: + """Fixture providing CrewMocker for agentic-fabric testing. + + This fixture builds on pytest-mock's mocker fixture and provides + specialized methods for mocking AI framework components. + + Yields: + CrewMocker instance with access to all mocking utilities. + + Example: + def test_my_runner(crew_mocker): + # Mock CrewAI + crew_mocker.mock_crewai() + mock_llm = crew_mocker.patch_get_llm() + + # Test code + from agentic_fabric.runners.crewai_runner import CrewAIRunner + runner = CrewAIRunner() + ... + """ + cm = CrewMocker(mocker=mocker) + yield cm + cm.restore_modules() + + +@pytest.fixture +def mock_frameworks(mocker: MockerFixture) -> Generator[dict[str, Any], None, None]: + """Fixture to mock all AI framework modules for unit testing. + + This is a convenience fixture that mocks all framework modules + (crewai, langgraph, strands) so tests can run without the actual + frameworks installed. + + Yields: + Dictionary mapping module names to their mock objects. + + Example: + def test_crewai_runner(mock_frameworks): + from agentic_fabric.runners.crewai_runner import CrewAIRunner + runner = CrewAIRunner() + # Test runner behavior with mocked framework + """ + cm = CrewMocker(mocker=mocker) + mocks = cm.mock_all_frameworks() + yield mocks + cm.restore_modules() + + +@pytest.fixture +def mock_crewai(mocker: MockerFixture) -> Generator[dict[str, Any], None, None]: + """Fixture to mock only CrewAI framework modules. + + Yields: + Dictionary with CrewAI mock objects. + """ + cm = CrewMocker(mocker=mocker) + mocks = cm.mock_crewai() + yield mocks + cm.restore_modules() + + +@pytest.fixture +def mock_langgraph(mocker: MockerFixture) -> Generator[dict[str, Any], None, None]: + """Fixture to mock only LangGraph framework modules. + + Yields: + Dictionary with LangGraph mock objects. + """ + cm = CrewMocker(mocker=mocker) + mocks = cm.mock_langgraph() + yield mocks + cm.restore_modules() + + +@pytest.fixture +def mock_strands(mocker: MockerFixture) -> Generator[dict[str, Any], None, None]: + """Fixture to mock only Strands framework modules. + + Yields: + Dictionary with Strands mock objects. + """ + cm = CrewMocker(mocker=mocker) + mocks = cm.mock_strands() + yield mocks + cm.restore_modules() diff --git a/packages/agentic-fabric/tests/conftest.py b/packages/agentic-fabric/tests/conftest.py new file mode 100644 index 0000000..be4e742 --- /dev/null +++ b/packages/agentic-fabric/tests/conftest.py @@ -0,0 +1,259 @@ +"""Pytest configuration for agentic-fabric tests.""" + +from __future__ import annotations + +import os + +from collections.abc import Generator +from pathlib import Path +from typing import Any +from unittest.mock import MagicMock, patch + +import pytest + +from tests._crew_mocker import crew_mocker, mock_crewai, mock_frameworks, mock_langgraph, mock_strands # noqa: F401 + + +def pytest_addoption(parser: Any) -> None: + """Register test-suite options for live E2E coverage.""" + parser.addoption( + "--e2e", + action="store_true", + default=False, + help="Run E2E tests that require real framework runtimes and external credentials", + ) + parser.addoption( + "--framework", + action="store", + choices=("crewai", "langgraph", "strands"), + default=None, + help="Run E2E tests for a single framework", + ) + + +def pytest_collection_modifyitems(config: Any, items: list[pytest.Item]) -> None: + """Skip live E2E tests unless explicitly enabled and filter by framework.""" + e2e_enabled = config.getoption("--e2e") + framework_filter = config.getoption("--framework") + + skip_e2e = pytest.mark.skip(reason="E2E tests disabled; pass --e2e to run them") + skip_framework = pytest.mark.skip(reason=f"Test not selected by --framework={framework_filter}") + framework_markers = {"crewai", "langgraph", "strands"} + + for item in items: + if "e2e" in item.keywords and not e2e_enabled: + item.add_marker(skip_e2e) + + if framework_filter: + test_frameworks = framework_markers.intersection(item.keywords) + if test_frameworks and framework_filter not in test_frameworks: + item.add_marker(skip_framework) + + +@pytest.fixture(autouse=True) +def mock_llm_env(request: pytest.FixtureRequest) -> Generator[None, Any, None]: + """Set up test environment with mocked LLM credentials.""" + if "e2e" in request.node.keywords: + yield + return + + # Set dummy API keys for testing (will be mocked anyway) + with patch.dict( + os.environ, + { + "OPENAI_API_KEY": "sk-test-mock-key", + "ANTHROPIC_API_KEY": "sk-ant-test-mock-key", + "CREWAI_TESTING": "true", + }, + ): + yield + + +@pytest.fixture +def temp_workspace(tmp_path: Path) -> Path: + """Create a temporary workspace with package structure.""" + # Create packages directory structure + packages_dir = tmp_path / "packages" + packages_dir.mkdir() + + # Create a mock otterfall package with .crewai structure + otterfall_dir = packages_dir / "otterfall" + otterfall_dir.mkdir() + + crewai_dir = otterfall_dir / ".crewai" + crewai_dir.mkdir() + + # Create minimal manifest (dict format, not list) + manifest = crewai_dir / "manifest.yaml" + manifest.write_text(""" +name: otterfall +description: Test package +crews: + test_crew: + description: A test crew + agents: crews/test_crew/agents.yaml + tasks: crews/test_crew/tasks.yaml +""") + + # Create crews directory + crews_dir = crewai_dir / "crews" / "test_crew" + crews_dir.mkdir(parents=True) + + # Create minimal agent and task configs + (crews_dir / "agents.yaml").write_text(""" +test_agent: + role: Test Agent + goal: Test goal + backstory: Test backstory +""") + + (crews_dir / "tasks.yaml").write_text(""" +test_task: + description: Test task description + expected_output: Test output + agent: test_agent +""") + + return tmp_path + + +@pytest.fixture +def mock_crew() -> MagicMock: + """Create a mock crew result.""" + result = MagicMock() + result.raw = {"output": "test output", "success": True} + return result + + +@pytest.fixture +def check_api_key() -> None: + """Skip live E2E tests when Anthropic credentials are unavailable.""" + if not os.environ.get("ANTHROPIC_API_KEY"): + pytest.skip("ANTHROPIC_API_KEY not set") + + +@pytest.fixture +def check_aws_credentials() -> None: + """Skip live E2E tests when AWS Bedrock credentials are unavailable.""" + has_key = os.environ.get("AWS_ACCESS_KEY_ID") + has_profile = os.environ.get("AWS_PROFILE") + if not (has_key or has_profile): + pytest.skip("AWS credentials not configured; set AWS_ACCESS_KEY_ID or AWS_PROFILE") + + +@pytest.fixture +def temp_crew_dir(tmp_path: Path) -> Path: + """Create a temporary crew directory with minimal structure.""" + crew_dir = tmp_path / "test_package" / ".crewai" + crew_dir.mkdir(parents=True) + (crew_dir / "crews").mkdir() + return crew_dir + + +@pytest.fixture +def simple_agent_config() -> dict[str, Any]: + """Get a simple agent configuration for runner tests.""" + return { + "role": "Test Agent", + "goal": "Answer simple questions accurately", + "backstory": "You are a helpful assistant focused on providing clear, concise answers.", + } + + +@pytest.fixture +def simple_task_config() -> dict[str, Any]: + """Get a simple task configuration for runner tests.""" + return { + "description": "Answer the question: What is 2 + 2?", + "expected_output": "The answer to the mathematical question", + } + + +@pytest.fixture +def simple_crew_config( + simple_agent_config: dict[str, Any], + simple_task_config: dict[str, Any], +) -> dict[str, Any]: + """Get a simple crew configuration for runner tests.""" + return { + "name": "test_crew", + "description": "A simple test crew", + "agents": {"test_agent": simple_agent_config}, + "tasks": { + "test_task": { + **simple_task_config, + "agent": "test_agent", + }, + }, + "knowledge_paths": [], + } + + +@pytest.fixture +def multi_agent_crew_config() -> dict[str, Any]: + """Get a multi-agent crew configuration for runner tests.""" + return { + "name": "multi_agent_crew", + "description": "A crew with multiple collaborating agents", + "agents": { + "researcher": { + "role": "Researcher", + "goal": "Gather and analyze information", + "backstory": "You are an expert researcher.", + }, + "writer": { + "role": "Writer", + "goal": "Write clear summaries", + "backstory": "You are a skilled technical writer.", + }, + }, + "tasks": { + "research_task": { + "description": "Research the topic: Python programming", + "expected_output": "Key facts about Python", + "agent": "researcher", + }, + "writing_task": { + "description": "Write a brief summary based on the research", + "expected_output": "A concise summary", + "agent": "writer", + "context": ["research_task"], + }, + }, + "knowledge_paths": [], + } + + +@pytest.fixture +def crew_with_knowledge(tmp_path: Path) -> dict[str, Any]: + """Get a crew configuration with a local knowledge source.""" + knowledge_dir = tmp_path / "knowledge" + knowledge_dir.mkdir() + (knowledge_dir / "test_info.md").write_text( + """# Test Knowledge + +This is test knowledge about the color blue. +Blue is a primary color. +It is often associated with calmness and stability. +""", + ) + + return { + "name": "knowledge_crew", + "description": "A crew with knowledge sources", + "agents": { + "knowledgeable_agent": { + "role": "Knowledge Expert", + "goal": "Answer questions using provided knowledge", + "backstory": "You have access to specialized knowledge.", + }, + }, + "tasks": { + "knowledge_task": { + "description": "What color is mentioned in the knowledge base?", + "expected_output": "The color mentioned", + "agent": "knowledgeable_agent", + }, + }, + "knowledge_paths": [knowledge_dir], + } diff --git a/packages/agentic-fabric/tests/e2e/README.md b/packages/agentic-fabric/tests/e2e/README.md new file mode 100644 index 0000000..ee45ff7 --- /dev/null +++ b/packages/agentic-fabric/tests/e2e/README.md @@ -0,0 +1,202 @@ +# End-to-End Tests + +This directory contains end-to-end tests for framework runners (CrewAI, LangGraph, Strands). + +## Overview + +E2E tests verify that each framework runner works correctly with: +- Real LLM API calls +- Complete crew execution flows +- Multi-agent collaboration +- Knowledge source integration +- Tool usage + +## Running E2E Tests + +E2E tests are **disabled by default** to avoid: +- Unexpected API costs +- Network dependency in CI/CD +- Slow test execution + +### Run all E2E tests + +```bash +# Requires ANTHROPIC_API_KEY environment variable +export ANTHROPIC_API_KEY="your-api-key" +uv run pytest tests/e2e/ --e2e -v +``` + +### Run framework-specific tests + +```bash +# Only CrewAI tests +uv run pytest tests/e2e/ --e2e --framework=crewai -v + +# Only LangGraph tests (requires langgraph installed) +uv run pytest tests/e2e/ --e2e --framework=langgraph -v + +# Only Strands tests (requires strands-agents installed) +uv run pytest tests/e2e/ --e2e --framework=strands -v +``` + +### Run specific test + +```bash +uv run pytest tests/e2e/test_crewai_e2e.py::TestCrewAISimpleExecution::test_simple_crew_execution --e2e -v +``` + +## Environment Variables + +### Required + +- `ANTHROPIC_API_KEY` - For Claude LLM (CrewAI, LangGraph) + +### Optional (for Strands with AWS Bedrock) + +- `AWS_ACCESS_KEY_ID` - AWS credentials +- `AWS_SECRET_ACCESS_KEY` - AWS credentials +- `AWS_REGION` - AWS region (e.g., `us-west-2`) + +#### Bedrock Model IDs + +When configuring Strands tests with Bedrock, use the official model IDs from [Claude on Amazon Bedrock](https://platform.claude.com/docs/en/build-with-claude/claude-on-amazon-bedrock): + +| Model | Bedrock Model ID | +|-------|------------------| +| Claude Haiku 4.5 ⭐ | `anthropic.claude-haiku-4-5-20251001-v1:0` | +| Claude Sonnet 4.5 | `anthropic.claude-sonnet-4-5-20250929-v1:0` | +| Claude Sonnet 4 | `anthropic.claude-sonnet-4-20250514-v1:0` | + +**Recommended**: Use Claude Haiku 4.5 for E2E tests - it's fast, cost-effective, and capable. + +Example configuration: +```python +crew_config = { + "llm": { + "provider": "bedrock", + "model": "anthropic.claude-haiku-4-5-20251001-v1:0", + }, + # ... rest of config +} +``` + +## Test Structure + +``` +tests/e2e/ +├── conftest.py # E2E fixtures and pytest configuration +├── test_crewai_e2e.py # CrewAI runner E2E tests +├── test_langgraph_e2e.py # LangGraph runner E2E tests +└── test_strands_e2e.py # Strands runner E2E tests +``` + +## Test Coverage + +### CrewAI (`test_crewai_e2e.py`) + +- ✅ Simple single-agent crew execution +- ✅ Build and run convenience method +- ✅ Multi-agent crew collaboration +- ✅ Knowledge source integration +- ✅ Tool usage with agents + +### LangGraph (`test_langgraph_e2e.py`) + +- ✅ ReAct agent execution +- ✅ Build and run method +- ✅ Multi-step graph execution +- ✅ State management between nodes +- ✅ Tool usage with agents + +### Strands (`test_strands_e2e.py`) + +- ✅ Simple agent execution +- ✅ Build and run method +- ✅ Agent building from configuration +- ✅ Task building +- ✅ System prompt generation +- ✅ AWS Bedrock integration (if credentials available) +- ✅ Tool usage (basic) + +## CI/CD Integration + +E2E tests are **excluded from regular CI runs** in `.github/workflows/ci.yml`: + +```yaml +- name: Run tests + run: uv run pytest tests -v --ignore=tests/e2e +``` + +To run E2E tests in CI (e.g., on a schedule): + +```yaml +# .github/workflows/e2e.yml +name: E2E Tests +on: + schedule: + - cron: '0 0 * * 0' # Weekly on Sunday + workflow_dispatch: + +jobs: + e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: hynek/setup-cached-uv@v2 + - run: uv sync --extra tests --extra crewai --extra langgraph --extra strands + - run: uv run pytest tests/e2e/ --e2e -v + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} +``` + +## Writing New E2E Tests + +1. Mark tests with `@pytest.mark.e2e` and framework marker +2. Use `check_api_key` fixture to verify required credentials +3. Keep tests focused and minimal to reduce API costs +4. Use fixtures from `conftest.py` for common configurations + +Note: Tests marked with `@pytest.mark.e2e` are automatically skipped unless the `--e2e` flag is provided. + +Example: + +```python +@pytest.mark.e2e +@pytest.mark.crewai +def test_my_feature( + check_api_key: None, + simple_crew_config: dict[str, Any], +) -> None: + """Test my new feature.""" + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + runner = CrewAIRunner() + result = runner.build_and_run(simple_crew_config, {"input": "test"}) + assert result is not None +``` + +## Debugging + +If tests fail: + +1. Check API key is set: `echo $ANTHROPIC_API_KEY` +2. Verify framework is installed: `pip list | grep crewai` +3. Run with verbose output: `-vv` +4. Run single test to isolate issue +5. Check network connectivity to LLM APIs + +## Cost Considerations + +E2E tests make real API calls which incur costs: + +- **CrewAI tests**: ~5-10 tests × ~$0.01-0.05 per test = ~$0.05-0.50 +- **LangGraph tests**: ~5 tests × ~$0.01-0.05 per test = ~$0.05-0.25 +- **Strands tests**: ~6-7 tests × $0.01-0.05 per test = ~$0.06-0.35 + +**Total estimated cost per full E2E run**: ~$0.16-1.10 + +To minimize costs: +- Run E2E tests only when needed +- Use `--framework` filter for specific tests +- Run on schedule instead of per-PR +- Use test timeouts to prevent runaway costs diff --git a/packages/agentic-fabric/tests/e2e/__init__.py b/packages/agentic-fabric/tests/e2e/__init__.py new file mode 100644 index 0000000..af68b0d --- /dev/null +++ b/packages/agentic-fabric/tests/e2e/__init__.py @@ -0,0 +1,7 @@ +"""End-to-end tests for framework runners. + +These tests verify that each framework runner works with real crews and LLM calls. +They are disabled by default and require the --e2e flag to run. +""" + +from __future__ import annotations diff --git a/packages/agentic-fabric/tests/e2e/conftest.py b/packages/agentic-fabric/tests/e2e/conftest.py new file mode 100644 index 0000000..b27fd66 --- /dev/null +++ b/packages/agentic-fabric/tests/e2e/conftest.py @@ -0,0 +1,21 @@ +"""E2E test configuration. + +Fixtures and CLI options are provided by pytest-agentic-fabric plugin. +This file is kept for any project-specific customizations. +""" + +from __future__ import annotations + +# All fixtures come from tests/conftest.py: +# - check_api_key +# - check_aws_credentials +# - simple_agent_config +# - simple_task_config +# - simple_crew_config +# - multi_agent_crew_config +# - crew_with_knowledge +# - temp_crew_dir +# +# CLI options: +# - --e2e: Enable E2E tests +# - --framework=: Filter by framework diff --git a/packages/agentic-fabric/tests/e2e/test_crewai_e2e.py b/packages/agentic-fabric/tests/e2e/test_crewai_e2e.py new file mode 100644 index 0000000..fb9877d --- /dev/null +++ b/packages/agentic-fabric/tests/e2e/test_crewai_e2e.py @@ -0,0 +1,214 @@ +"""End-to-end tests for CrewAI runner. + +These tests verify that the CrewAI runner works correctly with real LLM calls. +They require: +- The --e2e flag to run +- ANTHROPIC_API_KEY environment variable +- CrewAI installed (pip install crewai[tools]) +""" + +from __future__ import annotations + +from typing import Any + +import pytest + + +# Check if CrewAI is available +try: + import crewai # noqa: F401 + + CREWAI_AVAILABLE = True +except ImportError: + CREWAI_AVAILABLE = False + +# Skip all tests if CrewAI is not installed +pytestmark = [ + pytest.mark.e2e, + pytest.mark.crewai, + pytest.mark.skipif(not CREWAI_AVAILABLE, reason="crewai not installed"), +] + + +class TestCrewAISimpleExecution: + """Test simple crew execution with CrewAI.""" + + def test_simple_crew_execution( + self, + check_api_key: None, + simple_crew_config: dict[str, Any], + ) -> None: + """Test execution of a simple single-agent, single-task crew. + + Args: + check_api_key: Fixture to check for required API keys. + simple_crew_config: Simple crew configuration fixture. + """ + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + runner = CrewAIRunner() + + # Build the crew + crew = runner.build_crew(simple_crew_config) + assert crew is not None + + # Run the crew with a simple input + inputs = {"input": "What is 2 + 2?"} + result = runner.run(crew, inputs) + + # Verify we got a response + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 + # The answer should contain '4' somewhere + assert "4" in result + + def test_crew_build_and_run( + self, + check_api_key: None, + simple_crew_config: dict[str, Any], + ) -> None: + """Test the convenience build_and_run method. + + Args: + check_api_key: Fixture to check for required API keys. + simple_crew_config: Simple crew configuration fixture. + """ + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + runner = CrewAIRunner() + + # Use build_and_run convenience method + inputs = {"input": "What is the capital of France?"} + result = runner.build_and_run(simple_crew_config, inputs) + + # Verify we got a response about Paris + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 + assert "Paris" in result or "paris" in result.lower() + + +class TestCrewAIMultiAgent: + """Test multi-agent collaboration with CrewAI.""" + + def test_multi_agent_crew( + self, + check_api_key: None, + multi_agent_crew_config: dict[str, Any], + ) -> None: + """Test multi-agent crew with sequential task execution. + + Args: + check_api_key: Fixture to check for required API keys. + multi_agent_crew_config: Multi-agent crew configuration fixture. + """ + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + runner = CrewAIRunner() + + # Build and run multi-agent crew + inputs = {"input": "Tell me about Python programming"} + result = runner.build_and_run(multi_agent_crew_config, inputs) + + # Verify we got a result from the collaborative effort + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 + # Should mention Python in some form + assert "Python" in result or "python" in result.lower() + + +class TestCrewAIKnowledge: + """Test knowledge source integration with CrewAI.""" + + def test_knowledge_source_integration( + self, + check_api_key: None, + crew_with_knowledge: dict[str, Any], + ) -> None: + """Test crew with knowledge sources loaded from files. + + Args: + check_api_key: Fixture to check for required API keys. + crew_with_knowledge: Crew config with knowledge sources fixture. + """ + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + runner = CrewAIRunner() + + # Build and run crew with knowledge + inputs = {"question": "What color is mentioned in the knowledge base?"} + result = runner.build_and_run(crew_with_knowledge, inputs) + + # Verify the crew accessed the knowledge + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 + # Should mention blue from the knowledge file + assert "blue" in result.lower() or "Blue" in result + + +class TestCrewAITools: + """Test tool usage with CrewAI.""" + + def test_tool_usage( + self, + check_api_key: None, + simple_agent_config: dict[str, Any], + ) -> None: + """Test crew with tools assigned to agents. + + Args: + check_api_key: Fixture to check for required API keys. + simple_agent_config: Simple agent configuration fixture. + """ + from crewai_tools import tool + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + # Create a simple tool + @tool("get_test_data") + def get_test_data(query: str) -> str: + """Get test data for a query. + + Args: + query: The query string. + + Returns: + Test data response. + """ + # Use the query parameter in the response for completeness + return f"Test data for '{query}': The answer is 42" + + runner = CrewAIRunner() + + # Build agent with tool + agent = runner.build_agent(simple_agent_config, tools=[get_test_data]) + assert agent is not None + + # Build a simple task + task_config = { + "description": "Use the get_test_data tool to find the answer", + "expected_output": "The answer from the tool", + } + task = runner.build_task(task_config, agent) + assert task is not None + + # Create a minimal crew + from crewai import Crew, Process + + crew = Crew( + agents=[agent], + tasks=[task], + process=Process.sequential, + verbose=True, + ) + + # Run the crew + result = crew.kickoff() + + # Verify the tool was used + assert result is not None + result_str = result.raw if hasattr(result, "raw") else str(result) + assert "42" in result_str diff --git a/packages/agentic-fabric/tests/e2e/test_langgraph_e2e.py b/packages/agentic-fabric/tests/e2e/test_langgraph_e2e.py new file mode 100644 index 0000000..b958997 --- /dev/null +++ b/packages/agentic-fabric/tests/e2e/test_langgraph_e2e.py @@ -0,0 +1,225 @@ +"""End-to-end tests for LangGraph runner. + +These tests verify that the LangGraph runner works correctly with real LLM calls. +They require: +- The --e2e flag to run +- ANTHROPIC_API_KEY environment variable +- LangGraph installed (pip install langgraph langchain-anthropic) +""" + +from __future__ import annotations + +from typing import Any + +import pytest + + +# Check if LangGraph is available +try: + import langgraph # noqa: F401 + + LANGGRAPH_AVAILABLE = True +except ImportError: + LANGGRAPH_AVAILABLE = False + +# Skip all tests if LangGraph is not installed +pytestmark = [ + pytest.mark.e2e, + pytest.mark.langgraph, + pytest.mark.skipif(not LANGGRAPH_AVAILABLE, reason="langgraph not installed"), +] + + +class TestLangGraphReActAgent: + """Test ReAct agent execution with LangGraph.""" + + def test_react_agent_execution( + self, + check_api_key: None, + simple_crew_config: dict[str, Any], + ) -> None: + """Test simple ReAct agent execution. + + Args: + check_api_key: Fixture to check for required API keys. + simple_crew_config: Simple crew configuration fixture. + """ + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + runner = LangGraphRunner() + + # Build the agent/crew + agent = runner.build_crew(simple_crew_config) + assert agent is not None + + # Run with simple input + inputs = {"input": "What is 3 + 5?"} + result = runner.run(agent, inputs) + + # Verify we got a response + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 + # Should contain '8' in the answer + assert "8" in result + + def test_build_and_run( + self, + check_api_key: None, + simple_crew_config: dict[str, Any], + ) -> None: + """Test the convenience build_and_run method. + + Args: + check_api_key: Fixture to check for required API keys. + simple_crew_config: Simple crew configuration fixture. + """ + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + runner = LangGraphRunner() + + # Use build_and_run convenience method + inputs = {"input": "What is the largest ocean on Earth?"} + result = runner.build_and_run(simple_crew_config, inputs) + + # Verify we got a response about Pacific Ocean + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 + assert "Pacific" in result or "pacific" in result.lower() + + +class TestLangGraphFlow: + """Test graph flow execution with LangGraph.""" + + def test_graph_flow( + self, + check_api_key: None, + simple_agent_config: dict[str, Any], + ) -> None: + """Test multi-step graph execution with state passing. + + Args: + check_api_key: Fixture to check for required API keys. + simple_agent_config: Simple agent configuration fixture. + """ + from langgraph.prebuilt import create_react_agent + + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + runner = LangGraphRunner() + + # Build agent with tools + llm = runner.get_llm() + agent = create_react_agent(llm, []) + assert agent is not None + + # Run the agent with a task that requires reasoning + result = agent.invoke( + { + "messages": [ + ( + "user", + "If a train travels 60 miles in 1 hour, how far does it travel in 30 minutes?", + ) + ] + } + ) + + # Verify we got a result with messages + assert result is not None + assert "messages" in result + assert len(result["messages"]) > 0 + + # Extract the final message + final_message = result["messages"][-1] + content = final_message.content if hasattr(final_message, "content") else str(final_message) + + # Should contain '30' miles + assert "30" in content + + +class TestLangGraphMultiStepPrompt: + """Test multi-step prompt handling with LangGraph.""" + + def test_agent_handles_multi_step_prompt( + self, + check_api_key: None, + multi_agent_crew_config: dict[str, Any], + ) -> None: + """Test that the LangGraph agent can handle a multi-step prompt. + + This test verifies that the created ReAct agent can process a prompt + with multiple sequential instructions. + + Args: + check_api_key: Fixture to check for required API keys. + multi_agent_crew_config: Multi-agent crew configuration fixture. + """ + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + runner = LangGraphRunner() + + # Build a crew from config + crew = runner.build_crew(multi_agent_crew_config) + assert crew is not None + + # Execute with input that requires multi-step reasoning with distinct answers + inputs = {"input": "First, tell me what 5 + 3 equals, then tell me what 10 - 3 equals."} + result = runner.run(crew, inputs) + + # Verify the runner properly managed state through the workflow + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 + # Should contain both answers + assert "8" in result + assert "7" in result + + +class TestLangGraphTools: + """Test tool usage with LangGraph.""" + + def test_agent_with_tools( + self, + check_api_key: None, + ) -> None: + """Test LangGraph agent with custom tools. + + Args: + check_api_key: Fixture to check for required API keys. + """ + from langchain_core.tools import tool + from langgraph.prebuilt import create_react_agent + + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + # Create a test tool + @tool + def get_magic_number() -> int: + """Get the magic number. + + Returns: + The magic number (always 42). + """ + return 42 + + runner = LangGraphRunner() + llm = runner.get_llm() + + # Create agent with tool + agent = create_react_agent(llm, [get_magic_number]) + + # Run agent that should use the tool + result = agent.invoke({"messages": [("user", "What is the magic number? Use the get_magic_number tool.")]}) + + # Verify we got a result + assert result is not None + assert "messages" in result + + # Extract final message + final_message = result["messages"][-1] + content = final_message.content if hasattr(final_message, "content") else str(final_message) + + # Should mention 42 + assert "42" in content diff --git a/packages/agentic-fabric/tests/e2e/test_strands_e2e.py b/packages/agentic-fabric/tests/e2e/test_strands_e2e.py new file mode 100644 index 0000000..922df55 --- /dev/null +++ b/packages/agentic-fabric/tests/e2e/test_strands_e2e.py @@ -0,0 +1,264 @@ +"""End-to-end tests for Strands runner. + +These tests verify that the Strands runner works correctly with real LLM calls. +They require: +- The --e2e flag to run +- AWS credentials configured (for Bedrock integration) +- Strands installed (pip install strands-agents) + +Note: Some tests may be skipped if AWS Bedrock is not configured. +""" + +from __future__ import annotations + +import os + +from typing import Any + +import pytest + + +# Check if Strands is available +try: + import strands # noqa: F401 + + STRANDS_AVAILABLE = True +except ImportError: + STRANDS_AVAILABLE = False + +# Skip all tests if Strands is not installed +pytestmark = [ + pytest.mark.e2e, + pytest.mark.strands, + pytest.mark.skipif(not STRANDS_AVAILABLE, reason="strands not installed"), +] + + +class TestStrandsAgentExecution: + """Test basic agent execution with Strands.""" + + def test_strands_agent_execution( + self, + check_aws_credentials: None, + simple_crew_config: dict[str, Any], + ) -> None: + """Test simple Strands agent execution. + + Args: + check_aws_credentials: Fixture to check for required AWS credentials. + simple_crew_config: Simple crew configuration fixture. + """ + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + # Build the agent from crew config + agent = runner.build_crew(simple_crew_config) + assert agent is not None + + # Run with simple input + inputs = {"input": "What is 7 + 3?"} + result = runner.run(agent, inputs) + + # Verify we got a response + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 + # Should contain '10' in the answer + assert "10" in result + + def test_build_and_run( + self, + check_aws_credentials: None, + simple_crew_config: dict[str, Any], + ) -> None: + """Test the convenience build_and_run method. + + Args: + check_aws_credentials: Fixture to check for required AWS credentials. + simple_crew_config: Simple crew configuration fixture. + """ + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + # Use build_and_run convenience method + inputs = {"input": "What is the smallest prime number?"} + result = runner.build_and_run(simple_crew_config, inputs) + + # Verify we got a response + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 + # Should mention 2 as the smallest prime + assert "2" in result + + +class TestStrandsAgentBuilder: + """Test Strands agent building from configuration.""" + + def test_build_agent( + self, + check_aws_credentials: None, + simple_agent_config: dict[str, Any], + ) -> None: + """Test building a Strands agent from configuration. + + Args: + check_aws_credentials: Fixture to check for required AWS credentials. + simple_agent_config: Simple agent configuration fixture. + """ + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + # Build agent + agent = runner.build_agent(simple_agent_config) + assert agent is not None + + # Test the agent with a simple query + result = agent("What is the capital of Italy?") + assert result is not None + result_str = str(result) + assert "Rome" in result_str or "rome" in result_str.lower() + + def test_build_task( + self, + check_aws_credentials: None, + simple_agent_config: dict[str, Any], + simple_task_config: dict[str, Any], + ) -> None: + """Test building a task for Strands. + + Args: + check_aws_credentials: Fixture to check for required AWS credentials. + simple_agent_config: Simple agent configuration fixture. + simple_task_config: Simple task configuration fixture. + """ + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + # Build agent first + agent = runner.build_agent(simple_agent_config) + + # Build task + task = runner.build_task(simple_task_config, agent) + assert task is not None + assert isinstance(task, dict) + assert "description" in task + assert "expected_output" in task + assert "agent" in task + + +class TestStrandsSystemPrompt: + """Test Strands system prompt generation.""" + + def test_system_prompt_generation( + self, + check_aws_credentials: None, + multi_agent_crew_config: dict[str, Any], + ) -> None: + """Test that system prompts are generated correctly from crew config. + + Args: + check_aws_credentials: Fixture to check for required AWS credentials. + multi_agent_crew_config: Multi-agent crew configuration fixture. + """ + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + # Build system prompt (internal method, testing through crew build) + agent = runner.build_crew(multi_agent_crew_config) + assert agent is not None + + # Run the agent to verify the system prompt is working + result = agent("Tell me about Python programming") + assert result is not None + result_str = str(result) + assert len(result_str) > 0 + + +class TestStrandsBedrockIntegration: + """Test Strands with AWS Bedrock (optional).""" + + @pytest.mark.skipif( + not os.environ.get("AWS_ACCESS_KEY_ID"), + reason="AWS credentials not configured", + ) + def test_bedrock_integration( + self, + check_aws_credentials: None, + simple_crew_config: dict[str, Any], + ) -> None: + """Test Strands with AWS Bedrock model provider. + + This test is skipped if AWS credentials are not available. + + Args: + check_aws_credentials: Fixture to check for required AWS credentials. + simple_crew_config: Simple crew configuration fixture. + """ + from agentic_fabric.runners.strands_runner import StrandsRunner + + # Add Bedrock model configuration + bedrock_config = { + **simple_crew_config, + "llm": { + "provider": "bedrock", + "model": "anthropic.claude-haiku-4-5-20251001-v1:0", + }, + } + + runner = StrandsRunner() + + # Build and run with Bedrock + inputs = {"input": "What is 5 * 6?"} + result = runner.build_and_run(bedrock_config, inputs) + + # Verify we got a response + assert result is not None + assert isinstance(result, str) + assert len(result) > 0 + # Should contain '30' in the answer + assert "30" in result + + +class TestStrandsTools: + """Test tool usage with Strands.""" + + def test_agent_with_tools( + self, + check_aws_credentials: None, + simple_agent_config: dict[str, Any], + ) -> None: + """Test Strands agent with custom tools. + + Args: + check_aws_credentials: Fixture to check for required AWS credentials. + simple_agent_config: Simple agent configuration fixture. + """ + from agentic_fabric.runners.strands_runner import StrandsRunner + + # Create a simple Python function as a tool + def get_secret_number() -> int: + """Get the secret number. + + Returns: + The secret number (always 99). + """ + return 99 + + runner = StrandsRunner() + + # Build agent with tool + agent = runner.build_agent(simple_agent_config, tools=[get_secret_number]) + assert agent is not None + + # Test execution with a prompt that encourages tool use + result = agent("Use the get_secret_number tool to find the secret number.") + assert result is not None + result_str = str(result) + # Should mention 99 if the tool was invoked + assert "99" in result_str diff --git a/packages/agentic-fabric/tests/test_agentic_data.py b/packages/agentic-fabric/tests/test_agentic_data.py new file mode 100644 index 0000000..fda0e39 --- /dev/null +++ b/packages/agentic-fabric/tests/test_agentic_data.py @@ -0,0 +1,85 @@ +"""Tests for AgenticData runtime context and dispatch.""" + +from __future__ import annotations + +from typing import Any + +import pytest + +from agentic_fabric import AgenticData +from agentic_fabric.runners.registry import RuntimeUnavailableError + + +def test_agentic_data_preserves_value_and_runtime_context(monkeypatch: pytest.MonkeyPatch) -> None: + """AgenticData should keep data and active runtime context together.""" + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: runtime == "langgraph") + + data = AgenticData({"task": "summarize"}) + data.use_runtime("langgraph") + data.cast({"task": "rewrite"}) + + assert data.active_runtime == "langgraph" + assert data.as_builtin() == {"task": "rewrite"} + + +def test_use_runtime_strict_reports_install_guidance(monkeypatch: pytest.MonkeyPatch) -> None: + """Unavailable runtimes should raise a typed error with install guidance.""" + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: False) + + with pytest.raises(RuntimeUnavailableError, match="agentic-fabric\\[crewai\\]"): + AgenticData().use_runtime("crewai") + + +def test_select_runtime_prefers_explicit_runtime(monkeypatch: pytest.MonkeyPatch) -> None: + """Explicit runtime selection should override an active runtime.""" + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: runtime == "strands") + + data = AgenticData(active_runtime="langgraph") + + assert data.select_runtime("strands") == "strands" + + +def test_select_runtime_rejects_active_manifest_conflict(monkeypatch: pytest.MonkeyPatch) -> None: + """Active runtime and required manifest runtime should not silently conflict.""" + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: True) + data = AgenticData(active_runtime="langgraph") + + with pytest.raises(ValueError, match="conflicts"): + data.select_runtime(crew_config={"required_framework": "crewai"}) + + +def test_run_agent_uses_registered_crew_and_runtime(monkeypatch: pytest.MonkeyPatch) -> None: + """Named agents should route through run_crew_auto with merged inputs.""" + calls: list[tuple[dict[str, Any], dict[str, Any], str | None]] = [] + + def fake_run_crew_auto(crew_config: dict[str, Any], inputs: dict[str, Any], framework: str | None = None) -> str: + calls.append((crew_config, inputs, framework)) + return "done" + + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: runtime == "crewai") + monkeypatch.setattr("agentic_fabric.core.decomposer.run_crew_auto", fake_run_crew_auto) + + data = AgenticData(agent_registry={"reviewer": {"name": "review", "agents": {}, "tasks": {}}}) + + result = data.run_reviewer({"code": "x"}, severity="high") + + assert result == "done" + assert calls == [({"name": "review", "agents": {}, "tasks": {}}, {"code": "x", "severity": "high"}, "crewai")] + + +def test_unknown_agent_lists_registered_names() -> None: + """Unknown agent errors should include the registered names.""" + data = AgenticData(agent_registry={"reviewer": {"name": "review"}}) + + with pytest.raises(KeyError, match="reviewer"): + data.run_agent("writer") + + +def test_fallback_vendor_guidance_when_vendor_fabric_missing() -> None: + """The no-vendor fallback should fail with install guidance.""" + data = AgenticData() + if data.vendor_fabric_available: + pytest.skip("vendor-fabric is installed in this environment") + + with pytest.raises(ImportError, match="vendor-fabric"): + data.open("github") diff --git a/packages/agentic-fabric/tests/test_capabilities.py b/packages/agentic-fabric/tests/test_capabilities.py new file mode 100644 index 0000000..f38ec24 --- /dev/null +++ b/packages/agentic-fabric/tests/test_capabilities.py @@ -0,0 +1,57 @@ +"""Tests for agent capability metadata and dispatch.""" + +from __future__ import annotations + +from typing import Any + +import pytest + +from agentic_fabric.capabilities import agent_capability +from agentic_fabric.runners.base import BaseRunner + + +class TestCapabilityCollection: + """Capability decorators should be collected at class creation.""" + + def test_base_runner_declares_runtime_capabilities(self) -> None: + """Runner methods should expose read-only capability metadata.""" + + class TestRunner(BaseRunner): + def build_crew(self, crew_config: dict[str, Any]) -> Any: + return crew_config + + def run(self, crew: Any, inputs: dict[str, Any]) -> str: + return f"{crew}:{inputs}" + + def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: + return agent_config + + def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: + return task_config, agent + + runner = TestRunner() + + assert "build_crew" in runner.capability_map() + assert "run_crew" in runner.capability_map() + assert runner.call_capability("run_crew", {"name": "crew"}, {"task": "go"}) == "{'name': 'crew'}:{'task': 'go'}" + + with pytest.raises(TypeError): + runner.capability_map()["new"] = runner.capability_map()["run"] # type: ignore[index] + + def test_subclass_decorator_adds_custom_capability(self) -> None: + """Custom decorated methods should be dispatchable by alias.""" + + class CustomProvider: + @agent_capability("summarize", aliases=("summary",), description="Summarize text.") + def summarize(self, text: str) -> str: + return text.upper() + + from agentic_fabric.capabilities import AgentCapabilityProviderMixin + + class TestProvider(AgentCapabilityProviderMixin, CustomProvider): + pass + + provider = TestProvider() + + assert provider.agent_capabilities["summary"].name == "summarize" + assert provider.call_capability("summary", "hello") == "HELLO" diff --git a/packages/agentic-fabric/tests/test_cli_smoke.py b/packages/agentic-fabric/tests/test_cli_smoke.py new file mode 100644 index 0000000..eaf492f --- /dev/null +++ b/packages/agentic-fabric/tests/test_cli_smoke.py @@ -0,0 +1,134 @@ +"""Smoke tests for CLI entry points.""" + +from __future__ import annotations + +from unittest.mock import patch + +import pytest + +from agentic_fabric.main import main + + +class TestCLIEntryPoint: + """Smoke tests for the agentic-fabric CLI.""" + + def test_no_args_prints_help(self, capsys) -> None: + """Running with no args should print help text.""" + with patch("sys.argv", ["agentic-fabric"]): + main() + captured = capsys.readouterr() + assert "agentic-fabric" in captured.out.lower() or "usage" in captured.out.lower() + + def test_help_flag(self) -> None: + """--help should exit 0.""" + with ( + patch("sys.argv", ["agentic-fabric", "--help"]), + pytest.raises(SystemExit) as exc_info, + ): + main() + assert exc_info.value.code == 0 + + def test_list_command_with_no_packages(self, tmp_path, capsys) -> None: + """'list' should work even with no packages found.""" + # Create empty packages dir + (tmp_path / "packages").mkdir() + + with ( + patch("sys.argv", ["agentic-fabric", "list"]), + patch( + "agentic_fabric.main.discover_packages", + return_value={}, + ), + patch( + "agentic_fabric.main.list_crews", + return_value={}, + ), + ): + main() + + captured = capsys.readouterr() + assert "No packages" in captured.out or "crews" in captured.out.lower() + + def test_list_command_json_output(self, capsys) -> None: + """'list --json' should produce valid JSON.""" + import json + + with ( + patch("sys.argv", ["agentic-fabric", "list", "--json"]), + patch( + "agentic_fabric.main.list_crews", + return_value={}, + ), + ): + main() + + captured = capsys.readouterr() + data = json.loads(captured.out) + assert "crews" in data + assert isinstance(data["crews"], list) + + def test_run_missing_package_and_crew_exits_2(self) -> None: + """'run' without package or crew should exit with code 2.""" + with ( + patch("sys.argv", ["agentic-fabric", "run"]), + pytest.raises(SystemExit) as exc_info, + ): + main() + assert exc_info.value.code == 2 + + def test_run_nonexistent_package_exits_2(self) -> None: + """'run' with a non-existent package should exit with code 2.""" + with ( + patch("sys.argv", ["agentic-fabric", "run", "nonexistent", "some_crew", "--input", "test"]), + patch("agentic_fabric.main.discover_packages", return_value={}), + pytest.raises(SystemExit) as exc_info, + ): + main() + assert exc_info.value.code == 2 + + def test_info_nonexistent_package_exits_2(self) -> None: + """'info' with non-existent package should exit with code 2.""" + with ( + patch("sys.argv", ["agentic-fabric", "info", "nonexistent", "some_crew"]), + patch("agentic_fabric.main.discover_packages", return_value={}), + pytest.raises(SystemExit) as exc_info, + ): + main() + assert exc_info.value.code == 2 + + def test_info_nonexistent_crew_exits_2(self, tmp_path) -> None: + """'info' with non-existent crew should exit with code 2.""" + config_dir = tmp_path / ".crewai" + config_dir.mkdir() + (config_dir / "manifest.yaml").write_text("crews: {}\n") + + with ( + patch("sys.argv", ["agentic-fabric", "info", "pkg", "missing_crew"]), + patch( + "agentic_fabric.main.discover_packages", + return_value={"pkg": config_dir}, + ), + pytest.raises(SystemExit) as exc_info, + ): + main() + assert exc_info.value.code == 2 + + +class TestModuleEntryPoint: + """Test running as python -m agentic_fabric.""" + + def test_module_is_importable(self) -> None: + """The __main__.py module should be importable.""" + import agentic_fabric.__main__ # noqa: F401 + + +class TestListRunnersCommand: + """Smoke tests for list-runners command.""" + + def test_list_runners_command(self, capsys) -> None: + """'list-runners' should execute without error.""" + with patch("sys.argv", ["agentic-fabric", "list-runners"]): + main() + captured = capsys.readouterr() + # Should show some output (either runners or header) + assert len(captured.out) > 0 diff --git a/packages/agentic-fabric/tests/test_connector_builder_crew.py b/packages/agentic-fabric/tests/test_connector_builder_crew.py new file mode 100644 index 0000000..d5f2116 --- /dev/null +++ b/packages/agentic-fabric/tests/test_connector_builder_crew.py @@ -0,0 +1,45 @@ +"""Tests for the Connector Builder Crew.""" + +from __future__ import annotations + +from unittest.mock import MagicMock, patch + +import pytest + + +pytest.importorskip("crewai", reason="crewai not installed") + +from agentic_fabric.crews.connector_builder.connector_builder_crew import ConnectorBuilderCrew + + +@patch("agentic_fabric.crews.connector_builder.connector_builder_crew.Crew") +@patch("agentic_fabric.crews.connector_builder.connector_builder_crew.Agent") +@patch("agentic_fabric.crews.connector_builder.connector_builder_crew.Task") +@patch("agentic_fabric.crews.connector_builder.connector_builder_crew.resolve_tools") +def test_connector_builder_crew( + mock_resolve_tools: MagicMock, + mock_task: MagicMock, + mock_agent: MagicMock, + mock_crew: MagicMock, +): + """Tests that the ConnectorBuilderCrew initializes correctly and can be kicked off.""" + mock_resolve_tools.side_effect = lambda tools: [f"resolved:{tool}" for tool in tools] + + # Test initialization + crew_instance = ConnectorBuilderCrew(output_dir="test_output") + + assert mock_agent.call_count == 3 + assert mock_task.call_count == 3 + assert crew_instance.crew is not None + mock_crew.assert_called_once() + first_agent_kwargs = mock_agent.call_args_list[0][1] + assert first_agent_kwargs["tools"] == ["resolved:ScrapeWebsiteTool", "resolved:CrawlWebsiteTool"] + + # Test kickoff + mock_crew_instance = mock_crew.return_value + mock_crew_instance.kickoff.return_value = "Success" + + result = crew_instance.kickoff(inputs={"url": "http://example.com"}) + + mock_crew_instance.kickoff.assert_called_once_with(inputs={"url": "http://example.com"}) + assert result == "Success" diff --git a/packages/agentic-fabric/tests/test_crew_mocker.py b/packages/agentic-fabric/tests/test_crew_mocker.py new file mode 100644 index 0000000..dcf1181 --- /dev/null +++ b/packages/agentic-fabric/tests/test_crew_mocker.py @@ -0,0 +1,73 @@ +"""Tests for local agentic-fabric test support.""" + +from __future__ import annotations + +import sys + +from pathlib import Path +from typing import TYPE_CHECKING + +from tests._crew_mocker import ALL_FRAMEWORK_MODULES, CREWAI_MODULES, LANGGRAPH_MODULES, STRANDS_MODULES + + +if TYPE_CHECKING: + from tests._crew_mocker import CrewMocker + + +class TestCrewMockerBasics: + """Test basic CrewMocker functionality.""" + + def test_crew_mocker_has_mocker(self, crew_mocker: CrewMocker) -> None: + assert crew_mocker.mocker is not None + + def test_magic_mock_property(self, crew_mocker: CrewMocker) -> None: + assert crew_mocker.MagicMock() is not None + + def test_mock_module_adds_to_sys_modules(self, crew_mocker: CrewMocker) -> None: + mock = crew_mocker.mock_module("test_fake_module_xyz") + assert sys.modules["test_fake_module_xyz"] is mock + + def test_mock_module_returns_same_mock_on_repeat(self, crew_mocker: CrewMocker) -> None: + assert crew_mocker.mock_module("test_fake_repeat") is crew_mocker.mock_module("test_fake_repeat") + + def test_restore_modules_cleans_up(self, crew_mocker: CrewMocker) -> None: + crew_mocker.mock_module("test_restore_target") + crew_mocker.restore_modules() + assert "test_restore_target" not in sys.modules + + +class TestFrameworkMocking: + """Test framework-specific mocking helpers.""" + + def test_mock_crewai_mocks_all_modules(self, crew_mocker: CrewMocker) -> None: + mocks = crew_mocker.mock_crewai() + for module in CREWAI_MODULES: + assert module in mocks + assert module in sys.modules + + def test_mock_langgraph_mocks_all_modules(self, crew_mocker: CrewMocker) -> None: + mocks = crew_mocker.mock_langgraph() + for module in LANGGRAPH_MODULES: + assert module in mocks + + def test_mock_strands_mocks_all_modules(self, crew_mocker: CrewMocker) -> None: + mocks = crew_mocker.mock_strands() + for module in STRANDS_MODULES: + assert module in mocks + + def test_mock_all_frameworks(self, crew_mocker: CrewMocker) -> None: + mocks = crew_mocker.mock_all_frameworks() + for module in ALL_FRAMEWORK_MODULES: + assert module in mocks + + def test_patch_get_llm_custom_return(self, crew_mocker: CrewMocker) -> None: + custom_llm = crew_mocker.MagicMock(name="custom_llm") + mock = crew_mocker.patch_get_llm(return_value=custom_llm) + assert mock.return_value is custom_llm + + +class TestLocalFixtures: + """Test reusable local fixtures.""" + + def test_temp_workspace_fixture(self, temp_workspace: Path) -> None: + assert (temp_workspace / "packages" / "otterfall" / ".crewai" / "manifest.yaml").exists() diff --git a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py new file mode 100644 index 0000000..4e49b92 --- /dev/null +++ b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py @@ -0,0 +1,200 @@ +"""Tests for framework auto-detection edge cases in the decomposer module.""" + +from __future__ import annotations + +from unittest.mock import patch + +import pytest + +from agentic_fabric.core.decomposer import ( + _framework_cache, + _get_install_command, + detect_framework, + get_available_frameworks, + get_framework_info, + is_framework_available, +) +from agentic_fabric.runners.registry import clear_runtime_cache + + +class TestIsFrameworkAvailable: + """Edge cases for is_framework_available.""" + + def setup_method(self): + """Clear the framework cache before each test.""" + _framework_cache.clear() + clear_runtime_cache() + clear_runtime_cache() + + def test_unsupported_framework_returns_false(self) -> None: + """An unsupported framework name should return False immediately.""" + result = is_framework_available("pytorch") + assert result is False + + def test_unsupported_framework_is_cached_as_false(self) -> None: + """Unsupported framework names should still be cached.""" + is_framework_available("unsupported_thing") + assert "unsupported_thing" in _framework_cache + assert _framework_cache["unsupported_thing"] is False + + def test_import_failure_returns_false(self) -> None: + """When importlib.import_module raises ImportError, return False.""" + with patch("agentic_fabric.core.decomposer.importlib.import_module", side_effect=ImportError("nope")): + result = is_framework_available("crewai") + assert result is False + + def test_successful_import_returns_true_and_caches(self) -> None: + """A successful import should cache True.""" + with patch("agentic_fabric.core.decomposer.importlib.import_module"): + result = is_framework_available("crewai") + assert result is True + assert _framework_cache["crewai"] is True + + def test_cache_hit_does_not_reimport(self) -> None: + """Once cached, no re-import should occur.""" + _framework_cache["strands"] = True + with patch("agentic_fabric.core.decomposer.importlib.import_module") as mock_import: + result = is_framework_available("strands") + assert result is True + mock_import.assert_not_called() + + def test_empty_string_framework_returns_false(self) -> None: + """Empty string framework name should return False.""" + result = is_framework_available("") + assert result is False + + +class TestDetectFramework: + """Edge cases for detect_framework.""" + + def test_preferred_auto_falls_through_to_detection(self) -> None: + """preferred='auto' should be treated as no preference.""" + + def mock_available(framework): + return framework == "strands" + + with patch( + "agentic_fabric.core.decomposer.is_framework_available", + side_effect=mock_available, + ): + result = detect_framework(preferred="auto") + + assert result == "strands" + + def test_preferred_unavailable_falls_back_gracefully(self) -> None: + """When preferred framework is unavailable, fall back to auto-detect.""" + + def mock_available(framework): + return framework == "crewai" + + with patch( + "agentic_fabric.core.decomposer.is_framework_available", + side_effect=mock_available, + ): + result = detect_framework(preferred="strands") + + assert result == "crewai" + + def test_preferred_none_auto_detects(self) -> None: + """preferred=None should auto-detect.""" + + def mock_available(framework): + return framework == "langgraph" + + with patch( + "agentic_fabric.core.decomposer.is_framework_available", + side_effect=mock_available, + ): + result = detect_framework(preferred=None) + + assert result == "langgraph" + + def test_crewai_has_highest_priority(self) -> None: + """When all frameworks available, crewai should win.""" + with patch( + "agentic_fabric.core.decomposer.is_framework_available", + return_value=True, + ): + result = detect_framework() + + assert result == "crewai" + + def test_error_message_lists_install_options(self) -> None: + """RuntimeError should include installation instructions.""" + with ( + patch("agentic_fabric.core.decomposer.is_framework_available", return_value=False), + pytest.raises(RuntimeError, match="pip install crewai"), + ): + detect_framework() + + +class TestGetAvailableFrameworks: + """Edge cases for get_available_frameworks.""" + + def test_returns_only_available(self) -> None: + """Should only include frameworks that are importable.""" + + def mock_available(framework): + return framework == "strands" + + with patch( + "agentic_fabric.core.decomposer.is_framework_available", + side_effect=mock_available, + ): + result = get_available_frameworks() + + assert result == ["strands"] + + def test_preserves_priority_order(self) -> None: + """Returned list should preserve priority order.""" + + def mock_available(framework): + return framework in ["strands", "crewai"] + + with patch( + "agentic_fabric.core.decomposer.is_framework_available", + side_effect=mock_available, + ): + result = get_available_frameworks() + + assert result == ["crewai", "strands"] + + +class TestFrameworkInfo: + """Tests for lazy runtime registry metadata.""" + + def setup_method(self): + """Clear the framework cache before each test.""" + _framework_cache.clear() + clear_runtime_cache() + + def test_framework_info_includes_install_guidance(self) -> None: + """Runtime registry metadata should expose extras and availability.""" + with patch("agentic_fabric.runners.registry.importlib.import_module", side_effect=ImportError("missing")): + info = get_framework_info("crewai") + + assert isinstance(info, dict) + assert info["name"] == "crewai" + assert info["extra"] == "crewai" + assert info["available"] is False + assert "agentic-fabric[crewai]" in info["install"] + + +class TestGetInstallCommand: + """Tests for _get_install_command.""" + + def test_crewai_includes_tools(self) -> None: + assert "crewai[tools]" in _get_install_command("crewai") + + def test_langgraph_includes_langchain(self) -> None: + result = _get_install_command("langgraph") + assert "langgraph" in result + assert "langchain" in result + + def test_strands_maps_correctly(self) -> None: + assert "strands-agents" in _get_install_command("strands") + + def test_unknown_framework_returns_itself(self) -> None: + """Unknown framework name should return as-is.""" + result = _get_install_command("unknown_framework") + assert result == "unknown_framework" diff --git a/packages/agentic-fabric/tests/test_discovery.py b/packages/agentic-fabric/tests/test_discovery.py new file mode 100644 index 0000000..6795d8f --- /dev/null +++ b/packages/agentic-fabric/tests/test_discovery.py @@ -0,0 +1,462 @@ +"""Tests for the discovery module.""" + +from __future__ import annotations + +from pathlib import Path +from unittest.mock import patch + +import pytest + + +class TestDiscovery: + """Tests for package discovery functionality.""" + + def test_discover_packages_finds_crewai_directories(self, temp_workspace: Path) -> None: + """Test that discover_packages finds packages with .crewai directories.""" + from agentic_fabric.core.discovery import discover_packages + + packages = discover_packages(workspace_root=temp_workspace) + + assert "otterfall" in packages + assert packages["otterfall"].exists() + + def test_discover_packages_finds_crew_directories(self, tmp_path: Path) -> None: + """Test that discover_packages finds framework-agnostic .crew directories.""" + from agentic_fabric.core.discovery import discover_packages + + # Create packages with .crew directory + pkg_dir = tmp_path / "packages" / "strata" + crew_dir = pkg_dir / ".crew" + crew_dir.mkdir(parents=True) + (crew_dir / "manifest.yaml").write_text("name: strata\ncrews: {}") + + packages = discover_packages(workspace_root=tmp_path) + + assert "strata" in packages + assert packages["strata"].name == ".crew" + + def test_discover_packages_prefers_crew_over_crewai(self, tmp_path: Path) -> None: + """Test that .crew takes priority over .crewai when both exist.""" + from agentic_fabric.core.discovery import discover_packages + + # Create package with both .crew and .crewai + pkg_dir = tmp_path / "packages" / "hybrid" + (pkg_dir / ".crew").mkdir(parents=True) + (pkg_dir / ".crew" / "manifest.yaml").write_text("name: hybrid\ncrews: {}") + (pkg_dir / ".crewai").mkdir(parents=True) + (pkg_dir / ".crewai" / "manifest.yaml").write_text("name: hybrid\ncrews: {}") + + packages = discover_packages(workspace_root=tmp_path) + + assert "hybrid" in packages + # .crew should be preferred (framework-agnostic first) + assert packages["hybrid"].name == ".crew" + + def test_discover_packages_returns_empty_when_no_packages(self, tmp_path: Path) -> None: + """Test that discover_packages returns empty dict when no config dirs exist.""" + from agentic_fabric.core.discovery import discover_packages + + # Create empty packages directory + packages_dir = tmp_path / "packages" + packages_dir.mkdir() + (packages_dir / "some_package").mkdir() + + packages = discover_packages(workspace_root=tmp_path) + + assert packages == {} + + def test_discover_all_framework_configs(self, tmp_path: Path) -> None: + """Test discovering all framework configs for a package.""" + from agentic_fabric.core.discovery import discover_all_framework_configs + + # Create package with multiple framework configs + pkg_dir = tmp_path / "packages" / "multi" + (pkg_dir / ".crew").mkdir(parents=True) + (pkg_dir / ".crew" / "manifest.yaml").write_text("name: multi\ncrews: {}") + (pkg_dir / ".crewai").mkdir(parents=True) + (pkg_dir / ".crewai" / "manifest.yaml").write_text("name: multi\ncrews: {}") + + configs = discover_all_framework_configs(workspace_root=tmp_path) + + assert "multi" in configs + assert None in configs["multi"] # .crew -> None (agnostic) + assert "crewai" in configs["multi"] + + def test_list_crews_returns_crews_from_manifest(self, temp_workspace: Path) -> None: + """Test that list_crews returns crew definitions from manifest.""" + from agentic_fabric.core.discovery import list_crews + + with patch( + "agentic_fabric.core.discovery.discover_packages", + return_value={"otterfall": temp_workspace / "packages" / "otterfall" / ".crewai"}, + ): + crews_by_package = list_crews() + + assert "otterfall" in crews_by_package + crews = crews_by_package["otterfall"] + assert len(crews) == 1 + assert crews[0]["name"] == "test_crew" + + def test_list_crews_filters_by_package_name(self, temp_workspace: Path) -> None: + """Test that list_crews can filter to a specific package.""" + from agentic_fabric.core.discovery import list_crews + + with patch( + "agentic_fabric.core.discovery.discover_packages", + return_value={"otterfall": temp_workspace / "packages" / "otterfall" / ".crewai"}, + ): + crews_by_package = list_crews(package_name="otterfall") + + assert "otterfall" in crews_by_package + assert len(crews_by_package) == 1 + + def test_list_crews_returns_empty_for_nonexistent_package(self, temp_workspace: Path) -> None: + """Test that list_crews returns empty for non-existent package.""" + from agentic_fabric.core.discovery import list_crews + + with patch( + "agentic_fabric.core.discovery.discover_packages", + return_value={"otterfall": temp_workspace / "packages" / "otterfall" / ".crewai"}, + ): + crews_by_package = list_crews(package_name="nonexistent") + + assert crews_by_package == {} + + def test_load_manifest_parses_yaml(self, temp_workspace: Path) -> None: + """Test that load_manifest parses YAML correctly.""" + from agentic_fabric.core.discovery import load_manifest + + crewai_dir = temp_workspace / "packages" / "otterfall" / ".crewai" + manifest = load_manifest(crewai_dir) + + assert manifest is not None + assert manifest.get("name") == "otterfall" + assert "crews" in manifest + + def test_get_workspace_root_finds_root(self) -> None: + """Test that get_workspace_root finds the workspace root.""" + from agentic_fabric.core.discovery import get_workspace_root + + # This should find the actual workspace root + root = get_workspace_root() + + # Verify it looks like a workspace root + assert (root / "packages").exists() or root == Path.cwd() + + def test_get_framework_from_config_dir(self) -> None: + """Test framework detection from directory name.""" + from agentic_fabric.core.discovery import get_framework_from_config_dir + + assert get_framework_from_config_dir(Path("/some/path/.crew")) is None + assert get_framework_from_config_dir(Path("/some/path/.crewai")) == "crewai" + assert get_framework_from_config_dir(Path("/some/path/.langgraph")) == "langgraph" + assert get_framework_from_config_dir(Path("/some/path/.strands")) == "strands" + + def test_get_crew_config_includes_required_framework(self, temp_workspace: Path) -> None: + """Test that get_crew_config includes required_framework field.""" + from agentic_fabric.core.discovery import get_crew_config + + crewai_dir = temp_workspace / "packages" / "otterfall" / ".crewai" + config = get_crew_config(crewai_dir, "test_crew") + + assert config["required_framework"] == "crewai" + + +class TestDecomposer: + """Tests for the decomposer module.""" + + def test_is_framework_available_caches_results(self) -> None: + """Test that framework availability is cached.""" + from agentic_fabric.core.decomposer import _framework_cache, is_framework_available + + # Clear cache first + _framework_cache.clear() + + # Check availability (will cache result) + result1 = is_framework_available("nonexistent_framework") + result2 = is_framework_available("nonexistent_framework") + + assert result1 is False + assert result2 is False + assert "nonexistent_framework" in _framework_cache + + def test_detect_framework_raises_when_none_available(self) -> None: + """Test that detect_framework raises when no frameworks are available.""" + from agentic_fabric.core.decomposer import detect_framework + + with ( + patch("agentic_fabric.core.decomposer.is_framework_available", return_value=False), + pytest.raises(RuntimeError, match="No AI frameworks installed"), + ): + detect_framework() + + def test_detect_framework_respects_priority(self) -> None: + """Test that frameworks are detected in priority order.""" + from agentic_fabric.core.decomposer import detect_framework + + def mock_available(framework): + return framework in ["langgraph", "strands"] + + with patch( + "agentic_fabric.core.decomposer.is_framework_available", + side_effect=mock_available, + ): + result = detect_framework() + + # langgraph should be preferred over strands + assert result == "langgraph" + + def test_detect_framework_with_preferred_returns_preferred(self) -> None: + """Test that detect_framework returns preferred if available.""" + from agentic_fabric.core.decomposer import detect_framework + + def mock_available(framework): + return framework in ["crewai", "langgraph", "strands"] + + with patch( + "agentic_fabric.core.decomposer.is_framework_available", + side_effect=mock_available, + ): + result = detect_framework(preferred="strands") + + assert result == "strands" + + def test_detect_framework_falls_back_when_preferred_unavailable(self) -> None: + """Test that detect_framework falls back when preferred not available.""" + from agentic_fabric.core.decomposer import detect_framework + + def mock_available(framework): + return framework == "langgraph" + + with patch( + "agentic_fabric.core.decomposer.is_framework_available", + side_effect=mock_available, + ): + result = detect_framework(preferred="crewai") + + assert result == "langgraph" + + def test_get_available_frameworks_returns_list(self) -> None: + """Test that get_available_frameworks returns installed frameworks.""" + from agentic_fabric.core.decomposer import get_available_frameworks + + def mock_available(framework): + return framework in ["crewai", "strands"] + + with patch( + "agentic_fabric.core.decomposer.is_framework_available", + side_effect=mock_available, + ): + result = get_available_frameworks() + + assert isinstance(result, list) + assert "crewai" in result + assert "strands" in result + assert "langgraph" not in result + + def test_get_available_frameworks_returns_empty_when_none_installed(self) -> None: + """Test get_available_frameworks returns empty list when none installed.""" + from agentic_fabric.core.decomposer import get_available_frameworks + + with patch( + "agentic_fabric.core.decomposer.is_framework_available", + return_value=False, + ): + result = get_available_frameworks() + + assert result == [] + + def test_get_runner_returns_crewai_runner(self) -> None: + """Test that get_runner returns CrewAIRunner for crewai.""" + from unittest.mock import MagicMock + + from agentic_fabric.core.decomposer import get_runner + + mock_runner = MagicMock() + mock_runner.framework_name = "crewai" + + with ( + patch( + "agentic_fabric.core.decomposer.is_framework_available", + return_value=True, + ), + patch( + "agentic_fabric.runners.crewai_runner.CrewAIRunner", + return_value=mock_runner, + ), + ): + runner = get_runner("crewai") + + assert runner is not None + assert runner.framework_name == "crewai" + + def test_get_runner_returns_langgraph_runner(self) -> None: + """Test that get_runner returns LangGraphRunner for langgraph.""" + from unittest.mock import MagicMock + + from agentic_fabric.core.decomposer import get_runner + + mock_runner = MagicMock() + mock_runner.framework_name = "langgraph" + + with ( + patch( + "agentic_fabric.core.decomposer.is_framework_available", + return_value=True, + ), + patch( + "agentic_fabric.runners.langgraph_runner.LangGraphRunner", + return_value=mock_runner, + ), + ): + runner = get_runner("langgraph") + + assert runner is not None + assert runner.framework_name == "langgraph" + + def test_get_runner_returns_strands_runner(self) -> None: + """Test that get_runner returns StrandsRunner for strands.""" + from unittest.mock import MagicMock + + from agentic_fabric.core.decomposer import get_runner + + mock_runner = MagicMock() + mock_runner.framework_name = "strands" + + with ( + patch( + "agentic_fabric.core.decomposer.is_framework_available", + return_value=True, + ), + patch( + "agentic_fabric.runners.strands_runner.StrandsRunner", + return_value=mock_runner, + ), + ): + runner = get_runner("strands") + + assert runner is not None + assert runner.framework_name == "strands" + + def test_get_runner_auto_detects_framework(self) -> None: + """Test that get_runner with no args auto-detects framework.""" + from unittest.mock import MagicMock + + from agentic_fabric.core.decomposer import get_runner + + mock_runner = MagicMock() + mock_runner.framework_name = "strands" + + def mock_available(framework): + return framework == "strands" + + with ( + patch( + "agentic_fabric.core.decomposer.is_framework_available", + side_effect=mock_available, + ), + patch( + "agentic_fabric.runners.strands_runner.StrandsRunner", + return_value=mock_runner, + ), + ): + runner = get_runner() + + assert runner.framework_name == "strands" + + def test_get_runner_raises_for_unknown_framework(self) -> None: + """Test that get_runner raises for unknown framework.""" + from agentic_fabric.core.decomposer import get_runner + + with pytest.raises(ValueError, match="Unknown framework"): + get_runner("unknown_framework") + + def test_decompose_crew_uses_required_framework(self, tmp_path: Path) -> None: + """Test that decompose_crew respects required_framework from config.""" + from unittest.mock import MagicMock + + from agentic_fabric.core.decomposer import decompose_crew + + crew_config = { + "name": "test_crew", + "required_framework": "strands", + "agents": {}, + "tasks": {}, + } + + mock_runner = MagicMock() + mock_runner.framework_name = "strands" + mock_runner.build_crew.return_value = MagicMock() + + def mock_available(framework): + return framework in ["crewai", "strands"] + + with ( + patch( + "agentic_fabric.core.decomposer.is_framework_available", + side_effect=mock_available, + ), + patch( + "agentic_fabric.runners.strands_runner.StrandsRunner", + return_value=mock_runner, + ), + ): + # decompose_crew returns the result of runner.build_crew() + decompose_crew(crew_config) + + # Verify the runner's build_crew was called + mock_runner.build_crew.assert_called_once_with(crew_config) + + def test_decompose_crew_raises_when_required_unavailable(self) -> None: + """Test decompose_crew raises when required framework not available.""" + from agentic_fabric.core.decomposer import decompose_crew + + crew_config = { + "name": "test_crew", + "required_framework": "langgraph", + "agents": {}, + "tasks": {}, + } + + with ( + patch( + "agentic_fabric.core.decomposer.is_framework_available", + return_value=False, + ), + pytest.raises(RuntimeError, match=r"requires langgraph.*not installed"), + ): + decompose_crew(crew_config) + + def test_decompose_crew_validates_framework_conflict(self) -> None: + """Test decompose_crew validates requested vs required conflict.""" + from agentic_fabric.core.decomposer import decompose_crew + + crew_config = { + "name": "test_crew", + "required_framework": "crewai", + "agents": {}, + "tasks": {}, + } + + with ( + patch( + "agentic_fabric.core.decomposer.is_framework_available", + return_value=True, + ), + pytest.raises(ValueError, match=r"requires crewai.*langgraph was requested"), + ): + decompose_crew(crew_config, framework="langgraph") + + def test_get_install_command_returns_pip_install(self) -> None: + """Test that _get_install_command returns correct pip command.""" + from agentic_fabric.core.decomposer import _get_install_command + + result = _get_install_command("crewai") + assert "crewai" in result + + def test_get_install_command_maps_langgraph(self) -> None: + """Test that _get_install_command maps langgraph correctly.""" + from agentic_fabric.core.decomposer import _get_install_command + + result = _get_install_command("langgraph") + assert "langgraph" in result diff --git a/packages/agentic-fabric/tests/test_discovery_nested.py b/packages/agentic-fabric/tests/test_discovery_nested.py new file mode 100644 index 0000000..0a014df --- /dev/null +++ b/packages/agentic-fabric/tests/test_discovery_nested.py @@ -0,0 +1,168 @@ +"""Tests for crew discovery in nested directories and edge cases.""" + +from __future__ import annotations + +from pathlib import Path + +from agentic_fabric.core.discovery import ( + DIR_TO_FRAMEWORK, + FRAMEWORK_DIRS, + FRAMEWORK_TO_DIR, + discover_all_framework_configs, + discover_packages, +) + + +class TestDiscoverPackagesNested: + """Test discovery across nested directory structures.""" + + def test_skips_non_directory_entries(self, tmp_path: Path) -> None: + """Files in the packages/ directory should be ignored.""" + packages_dir = tmp_path / "packages" + packages_dir.mkdir() + + # Create a file (not a directory) in packages/ + (packages_dir / "README.md").write_text("# Packages") + + # Create a real package too + pkg_dir = packages_dir / "real_pkg" + crew_dir = pkg_dir / ".crew" + crew_dir.mkdir(parents=True) + (crew_dir / "manifest.yaml").write_text("name: real\ncrews: {}") + + packages = discover_packages(workspace_root=tmp_path) + assert "real_pkg" in packages + assert "README.md" not in packages + + def test_standalone_project_at_root(self, tmp_path: Path) -> None: + """A .crew directory at workspace root should be discovered.""" + crew_dir = tmp_path / ".crew" + crew_dir.mkdir() + (crew_dir / "manifest.yaml").write_text("name: root_project\ncrews: {}") + + packages = discover_packages(workspace_root=tmp_path) + assert tmp_path.name in packages + + def test_standalone_and_packages_coexist(self, tmp_path: Path) -> None: + """Root .crew and packages/.crew should both be discovered.""" + # Root level .crew + root_crew = tmp_path / ".crew" + root_crew.mkdir() + (root_crew / "manifest.yaml").write_text("name: root\ncrews: {}") + + # Package level .crew + pkg_dir = tmp_path / "packages" / "sub_pkg" + pkg_crew = pkg_dir / ".crew" + pkg_crew.mkdir(parents=True) + (pkg_crew / "manifest.yaml").write_text("name: sub\ncrews: {}") + + packages = discover_packages(workspace_root=tmp_path) + assert "sub_pkg" in packages + assert tmp_path.name in packages + + def test_packages_dir_not_present(self, tmp_path: Path) -> None: + """When there is no packages/ directory, only root is checked.""" + packages = discover_packages(workspace_root=tmp_path) + assert packages == {} + + def test_framework_filter_crewai(self, tmp_path: Path) -> None: + """Filtering by framework='crewai' should only find .crewai dirs.""" + pkg_dir = tmp_path / "packages" / "test_pkg" + + # Create .crew (agnostic) and .crewai (specific) + crew_dir = pkg_dir / ".crew" + crew_dir.mkdir(parents=True) + (crew_dir / "manifest.yaml").write_text("name: test\ncrews: {}") + + crewai_dir = pkg_dir / ".crewai" + crewai_dir.mkdir() + (crewai_dir / "manifest.yaml").write_text("name: test\ncrews: {}") + + packages = discover_packages(workspace_root=tmp_path, framework="crewai") + assert "test_pkg" in packages + assert packages["test_pkg"].name == ".crewai" + + def test_framework_filter_no_match(self, tmp_path: Path) -> None: + """Filtering by framework with no matching dirs returns empty.""" + pkg_dir = tmp_path / "packages" / "test_pkg" + crew_dir = pkg_dir / ".crewai" + crew_dir.mkdir(parents=True) + (crew_dir / "manifest.yaml").write_text("name: test\ncrews: {}") + + packages = discover_packages(workspace_root=tmp_path, framework="strands") + assert packages == {} + + def test_missing_manifest_skips_directory(self, tmp_path: Path) -> None: + """A .crewai directory without manifest.yaml should be skipped.""" + pkg_dir = tmp_path / "packages" / "test_pkg" + crewai_dir = pkg_dir / ".crewai" + crewai_dir.mkdir(parents=True) + # No manifest.yaml created + + packages = discover_packages(workspace_root=tmp_path) + assert packages == {} + + def test_multiple_packages_discovered(self, tmp_path: Path) -> None: + """Multiple packages should all be discovered.""" + packages_dir = tmp_path / "packages" + for name in ["alpha", "beta", "gamma"]: + crew_dir = packages_dir / name / ".crew" + crew_dir.mkdir(parents=True) + (crew_dir / "manifest.yaml").write_text(f"name: {name}\ncrews: {{}}") + + packages = discover_packages(workspace_root=tmp_path) + assert set(packages.keys()) == {"alpha", "beta", "gamma"} + + +class TestDiscoverAllFrameworkConfigs: + """Test discover_all_framework_configs.""" + + def test_multiple_frameworks_per_package(self, tmp_path: Path) -> None: + """A package with .crew, .crewai, and .strands should return all.""" + pkg_dir = tmp_path / "packages" / "multi" + + for dir_name in [".crew", ".crewai", ".strands"]: + fw_dir = pkg_dir / dir_name + fw_dir.mkdir(parents=True) + (fw_dir / "manifest.yaml").write_text(f"name: multi\nframework_dir: {dir_name}\ncrews: {{}}") + + configs = discover_all_framework_configs(workspace_root=tmp_path) + + assert "multi" in configs + assert None in configs["multi"] # .crew -> agnostic + assert "crewai" in configs["multi"] + assert "strands" in configs["multi"] + + def test_root_configs_discovered(self, tmp_path: Path) -> None: + """Root-level framework configs should be discovered.""" + crew_dir = tmp_path / ".langgraph" + crew_dir.mkdir() + (crew_dir / "manifest.yaml").write_text("name: root\ncrews: {}") + + configs = discover_all_framework_configs(workspace_root=tmp_path) + assert tmp_path.name in configs + assert "langgraph" in configs[tmp_path.name] + + def test_empty_workspace(self, tmp_path: Path) -> None: + """An empty workspace should return empty dict.""" + configs = discover_all_framework_configs(workspace_root=tmp_path) + assert configs == {} + + +class TestFrameworkConstants: + """Test the framework constant mappings.""" + + def test_dir_to_framework_completeness(self) -> None: + """Every entry in FRAMEWORK_DIRS should have a DIR_TO_FRAMEWORK entry.""" + for dir_name in FRAMEWORK_DIRS: + assert dir_name in DIR_TO_FRAMEWORK + + def test_framework_to_dir_completeness(self) -> None: + """Every framework in DIR_TO_FRAMEWORK should have a FRAMEWORK_TO_DIR entry.""" + for framework in DIR_TO_FRAMEWORK.values(): + assert framework in FRAMEWORK_TO_DIR + + def test_round_trip_mapping(self) -> None: + """DIR_TO_FRAMEWORK -> FRAMEWORK_TO_DIR should be a round trip.""" + for dir_name, framework in DIR_TO_FRAMEWORK.items(): + assert FRAMEWORK_TO_DIR[framework] == dir_name diff --git a/packages/agentic-fabric/tests/test_examples.py b/packages/agentic-fabric/tests/test_examples.py new file mode 100644 index 0000000..1597f06 --- /dev/null +++ b/packages/agentic-fabric/tests/test_examples.py @@ -0,0 +1,79 @@ +"""Integration checks for shipped examples.""" + +from __future__ import annotations + +import json +import subprocess +import sys + +from pathlib import Path + +from examples.discovery_workflow import DEFAULT_WORKSPACE, summarize_workspace +from examples.runtime_context import inspect_runtime_context +from examples.tool_registry import inspect_registry + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_discovery_workflow_summary_uses_bundled_workspace() -> None: + summary = summarize_workspace(DEFAULT_WORKSPACE) + + assert sorted(summary["packages"]) == ["review"] + review = summary["packages"]["review"] + assert review["config_dir"] == ".crew" + crew = review["crews"]["implementation_review"] + assert crew["required_framework"] is None + assert crew["agents"] == ["documenter", "reviewer"] + assert crew["tasks"] == ["review_code", "review_docs"] + + +def test_tool_registry_example_inspects_core_registry() -> None: + summary = inspect_registry() + + assert summary["framework_dirs"] == [".crew", ".crewai", ".langgraph", ".strands"] + assert summary["mcp_git_available"] is False + + +def test_runtime_context_example_inspects_agentic_data() -> None: + summary = inspect_runtime_context() + + assert summary["active_runtime"] == "crewai" + assert summary["registered_agents"] == ["reviewer"] + assert summary["runtime_names"] == ["crewai", "langgraph", "strands"] + + +def test_discovery_workflow_script_outputs_json() -> None: + result = subprocess.run( + [sys.executable, str(ROOT / "examples" / "discovery_workflow.py")], + check=True, + capture_output=True, + text=True, + ) + + parsed = json.loads(result.stdout) + assert "review" in parsed["packages"] + + +def test_tool_registry_script_outputs_json() -> None: + result = subprocess.run( + [sys.executable, str(ROOT / "examples" / "tool_registry.py")], + check=True, + capture_output=True, + text=True, + ) + + parsed = json.loads(result.stdout) + assert parsed["mcp_git_available"] is False + + +def test_runtime_context_script_outputs_json() -> None: + result = subprocess.run( + [sys.executable, str(ROOT / "examples" / "runtime_context.py")], + check=True, + capture_output=True, + text=True, + ) + + parsed = json.loads(result.stdout) + assert parsed["active_runtime"] == "crewai" diff --git a/packages/agentic-fabric/tests/test_file_tools.py b/packages/agentic-fabric/tests/test_file_tools.py new file mode 100644 index 0000000..f6f0386 --- /dev/null +++ b/packages/agentic-fabric/tests/test_file_tools.py @@ -0,0 +1,227 @@ +"""Tests for file manipulation tools. + +Note: These tests require crewai to be installed for the tool base classes. +""" + +from __future__ import annotations + +import os + +from pathlib import Path +from unittest.mock import patch + +import pytest + + +# Skip all tests if crewai not installed +pytest.importorskip("crewai", reason="crewai not installed") + + +class TestGetWorkspaceRoot: + """Tests for get_workspace_root function.""" + + def test_uses_target_package_env_var(self, temp_workspace: Path) -> None: + """Test that TARGET_PACKAGE env var is respected.""" + from agentic_fabric.tools.file_tools import get_workspace_root + + # Create another package + other_pkg = temp_workspace / "packages" / "other_package" + other_pkg.mkdir(parents=True) + + # Create pyproject.toml at workspace root + (temp_workspace / "pyproject.toml").write_text("[project]\nname = 'test'") + + with ( + patch.dict(os.environ, {"TARGET_PACKAGE": "other_package"}), + patch( + "agentic_fabric.tools.file_tools._find_workspace_root", + return_value=temp_workspace, + ), + ): + root = get_workspace_root() + + assert root == other_pkg + + def test_defaults_to_otterfall(self, temp_workspace: Path) -> None: + """Test that package defaults to 'otterfall'.""" + from agentic_fabric.tools.file_tools import get_workspace_root + + # Create otterfall package + otterfall = temp_workspace / "packages" / "otterfall" + otterfall.mkdir(parents=True, exist_ok=True) + + with patch( + "agentic_fabric.tools.file_tools._find_workspace_root", + return_value=temp_workspace, + ): + # Remove TARGET_PACKAGE if set + env = os.environ.copy() + env.pop("TARGET_PACKAGE", None) + with patch.dict(os.environ, env, clear=True): + root = get_workspace_root() + + assert root.name == "otterfall" + + def test_explicit_package_name(self, temp_workspace: Path) -> None: + """Test explicit package name parameter.""" + from agentic_fabric.tools.file_tools import get_workspace_root + + # Create custom package + custom_pkg = temp_workspace / "packages" / "custom" + custom_pkg.mkdir(parents=True) + + with patch( + "agentic_fabric.tools.file_tools._find_workspace_root", + return_value=temp_workspace, + ): + root = get_workspace_root(package_name="custom") + + assert root == custom_pkg + + +class TestGameCodeWriterTool: + """Tests for GameCodeWriterTool.""" + + def test_rejects_path_traversal(self) -> None: + """Test that path traversal is rejected.""" + from agentic_fabric.tools.file_tools import GameCodeWriterTool + + tool = GameCodeWriterTool() + result = tool._run(file_path="../../../etc/passwd", content="malicious") + + assert "Error" in result + assert "Path traversal" in result or "not allowed" in result + + def test_rejects_absolute_paths(self) -> None: + """Test that absolute paths are rejected.""" + from agentic_fabric.tools.file_tools import GameCodeWriterTool + + tool = GameCodeWriterTool() + result = tool._run(file_path="/etc/passwd", content="malicious") + + assert "Error" in result + + def test_rejects_disallowed_directories(self) -> None: + """Test that writes to non-allowed directories are rejected.""" + from agentic_fabric.tools.file_tools import GameCodeWriterTool + + tool = GameCodeWriterTool() + result = tool._run(file_path="node_modules/test.ts", content="test") + + assert "Error" in result + assert "not in an allowed directory" in result + + def test_rejects_disallowed_extensions(self) -> None: + """Test that disallowed file extensions are rejected.""" + from agentic_fabric.tools.file_tools import GameCodeWriterTool + + tool = GameCodeWriterTool() + result = tool._run(file_path="src/ecs/test.exe", content="binary") + + assert "Error" in result + assert "not allowed" in result + + def test_writes_to_allowed_directory(self, temp_workspace: Path) -> None: + """Test that writing to allowed directories works.""" + from agentic_fabric.tools.file_tools import GameCodeWriterTool + + # Create the allowed directory structure + ecs_dir = temp_workspace / "packages" / "otterfall" / "src" / "ecs" + ecs_dir.mkdir(parents=True) + + tool = GameCodeWriterTool() + + with patch( + "agentic_fabric.tools.file_tools.get_workspace_root", + return_value=temp_workspace / "packages" / "otterfall", + ): + result = tool._run( + file_path="src/ecs/TestComponent.ts", + content="export const TestComponent = {};", + ) + + assert "Successfully wrote" in result + assert (ecs_dir / "TestComponent.ts").exists() + + +class TestGameCodeReaderTool: + """Tests for GameCodeReaderTool.""" + + def test_rejects_path_traversal(self) -> None: + """Test that path traversal is rejected.""" + from agentic_fabric.tools.file_tools import GameCodeReaderTool + + tool = GameCodeReaderTool() + result = tool._run(file_path="../../../etc/passwd") + + assert "Error" in result + assert "not allowed" in result + + def test_reads_existing_file(self, temp_workspace: Path) -> None: + """Test reading an existing file.""" + from agentic_fabric.tools.file_tools import GameCodeReaderTool + + # Create a test file + test_file = temp_workspace / "packages" / "otterfall" / "src" / "test.ts" + test_file.parent.mkdir(parents=True, exist_ok=True) + test_file.write_text("export const Test = 'hello';") + + tool = GameCodeReaderTool() + + with patch( + "agentic_fabric.tools.file_tools.get_workspace_root", + return_value=temp_workspace / "packages" / "otterfall", + ): + result = tool._run(file_path="src/test.ts") + + assert "export const Test" in result + + def test_returns_error_for_missing_file(self, temp_workspace: Path) -> None: + """Test that missing files return an error.""" + from agentic_fabric.tools.file_tools import GameCodeReaderTool + + tool = GameCodeReaderTool() + + with patch( + "agentic_fabric.tools.file_tools.get_workspace_root", + return_value=temp_workspace / "packages" / "otterfall", + ): + result = tool._run(file_path="src/nonexistent.ts") + + assert "Error" in result + assert "not found" in result + + +class TestDirectoryListTool: + """Tests for DirectoryListTool.""" + + def test_lists_directory_contents(self, temp_workspace: Path) -> None: + """Test listing directory contents.""" + from agentic_fabric.tools.file_tools import DirectoryListTool + + # Create some test files + src_dir = temp_workspace / "packages" / "otterfall" / "src" + src_dir.mkdir(parents=True) + (src_dir / "index.ts").write_text("export {};") + (src_dir / "utils").mkdir() + + tool = DirectoryListTool() + + with patch( + "agentic_fabric.tools.file_tools.get_workspace_root", + return_value=temp_workspace / "packages" / "otterfall", + ): + result = tool._run(directory="src") + + assert "index.ts" in result + assert "utils" in result + + def test_rejects_path_traversal(self) -> None: + """Test that path traversal is rejected.""" + from agentic_fabric.tools.file_tools import DirectoryListTool + + tool = DirectoryListTool() + result = tool._run(directory="../../../etc") + + assert "Error" in result + assert "not allowed" in result diff --git a/packages/agentic-fabric/tests/test_loader.py b/packages/agentic-fabric/tests/test_loader.py new file mode 100644 index 0000000..31c6dd3 --- /dev/null +++ b/packages/agentic-fabric/tests/test_loader.py @@ -0,0 +1,163 @@ +"""Tests for the loader module. + +Note: These tests require crewai to be installed. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +import pytest + + +if TYPE_CHECKING: + from tests._crew_mocker import CrewMocker + +# Skip all tests in this module if crewai is not installed +pytestmark = pytest.mark.skipif( + not pytest.importorskip("crewai", reason="crewai not installed"), + reason="crewai not installed", +) + + +class TestCreateAgentFromConfig: + """Tests for create_agent_from_config function.""" + + def test_creates_agent_with_config(self, crew_mocker: CrewMocker) -> None: + """Test that create_agent_from_config creates an Agent.""" + from agentic_fabric.core.loader import create_agent_from_config + + config = { + "role": "Test Agent Role", + "goal": "Test goal", + "backstory": "Test backstory", + } + + MockAgent = crew_mocker.patch("crewai.Agent") + crew_mocker.patch_get_llm() + + create_agent_from_config("test_agent", config) + + MockAgent.assert_called_once() + call_kwargs = MockAgent.call_args[1] + assert call_kwargs["role"] == "Test Agent Role" + assert call_kwargs["goal"] == "Test goal" + assert call_kwargs["backstory"] == "Test backstory" + + def test_uses_agent_name_as_default_role(self, crew_mocker: CrewMocker) -> None: + """Test that agent name is used as default role.""" + from agentic_fabric.core.loader import create_agent_from_config + + config = {"goal": "Test goal", "backstory": "Test backstory"} + + MockAgent = crew_mocker.patch("crewai.Agent") + crew_mocker.patch_get_llm() + + create_agent_from_config("custom_agent_name", config) + + call_kwargs = MockAgent.call_args[1] + assert call_kwargs["role"] == "custom_agent_name" + + +class TestCreateTaskFromConfig: + """Tests for create_task_from_config function.""" + + def test_creates_task_with_config(self, crew_mocker: CrewMocker) -> None: + """Test that create_task_from_config creates a Task.""" + from agentic_fabric.core.loader import create_task_from_config + + config = { + "description": "Test task description", + "expected_output": "Test output", + } + mock_agent = crew_mocker.MagicMock() + + MockTask = crew_mocker.patch("crewai.Task") + + create_task_from_config("test_task", config, mock_agent) + + MockTask.assert_called_once() + call_kwargs = MockTask.call_args[1] + assert call_kwargs["description"] == "Test task description" + assert call_kwargs["expected_output"] == "Test output" + assert call_kwargs["agent"] == mock_agent + + +class TestLoadKnowledgeSources: + """Tests for load_knowledge_sources function.""" + + def test_loads_md_files(self, crew_mocker: CrewMocker, tmp_path: Path) -> None: + """Test that .md files are loaded as knowledge sources.""" + from agentic_fabric.core.loader import load_knowledge_sources + + # Create test knowledge directory with .md file + knowledge_dir = tmp_path / "knowledge" + knowledge_dir.mkdir() + (knowledge_dir / "test.md").write_text("# Test Knowledge\nSome content") + + MockKnowledgeSource = crew_mocker.patch_knowledge_source() + + load_knowledge_sources([knowledge_dir]) + + # Verify TextFileKnowledgeSource was called + assert MockKnowledgeSource.called + + def test_skips_nonexistent_paths(self, tmp_path: Path) -> None: + """Test that nonexistent paths are skipped.""" + from agentic_fabric.core.loader import load_knowledge_sources + + sources = load_knowledge_sources([tmp_path / "nonexistent"]) + + assert sources == [] + + +class TestLoadCrewFromConfig: + """Tests for load_crew_from_config function.""" + + def test_creates_crew_with_agents_and_tasks(self, crew_mocker: CrewMocker) -> None: + """Test that load_crew_from_config creates a complete Crew.""" + from agentic_fabric.core.loader import load_crew_from_config + + config = { + "name": "test_crew", + "agents": { + "test_agent": { + "role": "Test Agent", + "goal": "Test goal", + "backstory": "Test backstory", + } + }, + "tasks": { + "test_task": { + "description": "Test description", + "expected_output": "Test output", + "agent": "test_agent", + } + }, + "knowledge_paths": [], + } + + MockCrew = crew_mocker.patch_crewai_crew() + MockAgent = crew_mocker.patch_crewai_agent() + MockTask = crew_mocker.patch_crewai_task() + crew_mocker.patch_crewai_process() + crew_mocker.patch_get_llm() + + mock_agent = crew_mocker.MagicMock() + MockAgent.return_value = mock_agent + + mock_task = crew_mocker.MagicMock() + MockTask.return_value = mock_task + + load_crew_from_config(config) + + # Verify Agent was created + MockAgent.assert_called() + # Verify Task was created + MockTask.assert_called() + # Verify Crew was created with agents and tasks + MockCrew.assert_called_once() + call_kwargs = MockCrew.call_args[1] + assert len(call_kwargs["agents"]) == 1 + assert len(call_kwargs["tasks"]) == 1 diff --git a/packages/agentic-fabric/tests/test_manager.py b/packages/agentic-fabric/tests/test_manager.py new file mode 100644 index 0000000..6826a72 --- /dev/null +++ b/packages/agentic-fabric/tests/test_manager.py @@ -0,0 +1,394 @@ +"""Tests for the ManagerAgent class.""" + +from __future__ import annotations + +import asyncio + +from pathlib import Path +from unittest.mock import patch + +import pytest + +from agentic_fabric.core.manager import ManagerAgent + + +class TestManagerAgent: + """Tests for ManagerAgent base class.""" + + def test_init_with_crews(self): + """Test manager initialization with crew mappings.""" + crews = {"design": "game_design", "qa": "quality_assurance"} + manager = ManagerAgent(crews=crews) + + assert manager.crews == crews + assert manager.package_name is None + assert manager.workspace_root is None + + def test_init_with_package_name(self): + """Test manager initialization with package name.""" + crews = {"design": "game_design"} + manager = ManagerAgent(crews=crews, package_name="my_package") + + assert manager.package_name == "my_package" + + def test_get_packages_caches_result(self): + """Test that package discovery is cached.""" + manager = ManagerAgent(crews={"test": "test_crew"}) + + with patch("agentic_fabric.core.manager.discover_packages") as mock_discover: + mock_packages = {"pkg1": Path("/path/pkg1")} + mock_discover.return_value = mock_packages + + # First call + result1 = manager._get_packages() + assert result1 == mock_packages + assert mock_discover.call_count == 1 + + # Second call should use cache + result2 = manager._get_packages() + assert result2 == mock_packages + assert mock_discover.call_count == 1 # Not called again + + def test_delegate_with_string_input(self): + """Test delegation with string input.""" + manager = ManagerAgent( + crews={"design": "game_design"}, + package_name="test_pkg", + ) + + mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_config = { + "name": "game_design", + "agents": {}, + "tasks": {}, + } + + with ( + patch("agentic_fabric.core.manager.discover_packages") as mock_discover, + patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + ): + mock_discover.return_value = mock_packages + mock_get_config.return_value = mock_config + mock_run.return_value = "Design complete" + + result = manager.delegate("design", "Create a game design") + + assert result == "Design complete" + # Verify string was converted to dict + mock_run.assert_called_once() + call_args = mock_run.call_args + assert call_args[1]["inputs"] == {"task": "Create a game design"} + + def test_delegate_with_dict_input(self): + """Test delegation with dict input.""" + manager = ManagerAgent( + crews={"design": "game_design"}, + package_name="test_pkg", + ) + + mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_config = {"name": "game_design", "agents": {}, "tasks": {}} + + with ( + patch("agentic_fabric.core.manager.discover_packages") as mock_discover, + patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + ): + mock_discover.return_value = mock_packages + mock_get_config.return_value = mock_config + mock_run.return_value = "Design complete" + + inputs = {"task": "Create design", "theme": "fantasy"} + result = manager.delegate("design", inputs) + + assert result == "Design complete" + mock_run.assert_called_once() + call_args = mock_run.call_args + assert call_args[1]["inputs"] == inputs + + def test_delegate_unknown_role_raises_error(self): + """Test that delegating to unknown role raises ValueError.""" + manager = ManagerAgent(crews={"design": "game_design"}) + + with pytest.raises(ValueError, match="Unknown crew role 'unknown'"): + manager.delegate("unknown", "test task") + + def test_delegate_package_not_found_raises_error(self): + """Test that missing package raises ValueError.""" + manager = ManagerAgent( + crews={"design": "game_design"}, + package_name="nonexistent", + ) + + with patch("agentic_fabric.core.manager.discover_packages") as mock_discover: + mock_discover.return_value = {"other_pkg": Path("/other")} + + with pytest.raises(ValueError, match="Package 'nonexistent' not found"): + manager.delegate("design", "test task") + + def test_delegate_auto_discover_crew(self): + """Test auto-discovery of crew in packages when package_name not specified.""" + manager = ManagerAgent(crews={"design": "game_design"}) + + mock_packages = { + "pkg1": Path("/pkg1/.crewai"), + "pkg2": Path("/pkg2/.crewai"), + } + mock_config = {"name": "game_design", "agents": {}, "tasks": {}} + + with ( + patch("agentic_fabric.core.manager.discover_packages") as mock_discover, + patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + ): + mock_discover.return_value = mock_packages + + # First package doesn't have the crew + def get_config_side_effect(pkg_dir, crew_name): + if pkg_dir == Path("/pkg1/.crewai"): + raise ValueError("Crew not found") + return mock_config + + mock_get_config.side_effect = get_config_side_effect + mock_run.return_value = "Success" + + result = manager.delegate("design", "test task") + + assert result == "Success" + # Should have tried pkg1 (failed), then found in pkg2 (config is cached) + assert mock_get_config.call_count == 2 + + def test_delegate_crew_not_found_raises_error(self): + """Test that crew not found in any package raises ValueError.""" + manager = ManagerAgent(crews={"design": "nonexistent_crew"}) + + mock_packages = {"pkg1": Path("/pkg1/.crewai")} + + with ( + patch("agentic_fabric.core.manager.discover_packages") as mock_discover, + patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, + ): + mock_discover.return_value = mock_packages + mock_get_config.side_effect = ValueError("Crew not found") + + with pytest.raises(ValueError, match="Crew 'nonexistent_crew' not found"): + manager.delegate("design", "test task") + + @pytest.mark.asyncio + async def test_delegate_async(self): + """Test async delegation.""" + manager = ManagerAgent( + crews={"design": "game_design"}, + package_name="test_pkg", + ) + + mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_config = {"name": "game_design", "agents": {}, "tasks": {}} + + with ( + patch("agentic_fabric.core.manager.discover_packages") as mock_discover, + patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + ): + mock_discover.return_value = mock_packages + mock_get_config.return_value = mock_config + mock_run.return_value = "Async result" + + result = await manager.delegate_async("design", "test task") + + assert result == "Async result" + + @pytest.mark.asyncio + async def test_delegate_parallel(self): + """Test parallel delegation to multiple crews.""" + manager = ManagerAgent( + crews={"design": "game_design", "assets": "asset_gen"}, + package_name="test_pkg", + ) + + mock_packages = {"test_pkg": Path("/test/.crewai")} + + with ( + patch("agentic_fabric.core.manager.discover_packages") as mock_discover, + patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + ): + mock_discover.return_value = mock_packages + + # Return different configs for different crews to distinguish them + def get_config_side_effect(pkg_dir, crew_name): + return {"name": crew_name, "agents": {}, "tasks": {}} + + mock_get_config.side_effect = get_config_side_effect + + # Mock different results based on crew name in config + def run_side_effect(config, inputs, framework=None): + crew_name = config["name"] + if crew_name == "game_design": + return "Design done" + elif crew_name == "asset_gen": + return "Assets done" + return f"Unknown crew: {crew_name}" + + mock_run.side_effect = run_side_effect + + results = await manager.delegate_parallel( + [ + ("design", "Create design"), + ("assets", "Generate assets"), + ] + ) + + assert len(results) == 2 + # Verify correct results returned (order matches input order) + assert "Design done" in results + assert "Assets done" in results + # Both should have been executed + assert mock_run.call_count == 2 + + def test_delegate_sequential(self): + """Test sequential delegation to multiple crews.""" + manager = ManagerAgent( + crews={"design": "game_design", "impl": "implementation"}, + package_name="test_pkg", + ) + + mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_config = {"name": "test", "agents": {}, "tasks": {}} + + with ( + patch("agentic_fabric.core.manager.discover_packages") as mock_discover, + patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + ): + mock_discover.return_value = mock_packages + mock_get_config.return_value = mock_config + mock_run.side_effect = ["Design result", "Implementation result"] + + results = manager.delegate_sequential( + [ + ("design", "Create design"), + ("impl", "Implement design"), + ] + ) + + assert results == ["Design result", "Implementation result"] + assert mock_run.call_count == 2 + # Verify they were called in order + calls = mock_run.call_args_list + assert calls[0][1]["inputs"]["task"] == "Create design" + assert calls[1][1]["inputs"]["task"] == "Implement design" + + def test_checkpoint_auto_approve(self): + """Test checkpoint with auto_approve=True.""" + manager = ManagerAgent(crews={"test": "test_crew"}) + + approved, result = manager.checkpoint( + "Review design", + "Design output", + auto_approve=True, + ) + + assert approved is True + assert result == "Design output" + + def test_checkpoint_base_implementation_auto_approves(self): + """Test that base checkpoint implementation auto-approves.""" + manager = ManagerAgent(crews={"test": "test_crew"}) + + approved, result = manager.checkpoint( + "Review design", + "Design output", + auto_approve=False, # Even with False, base impl approves + ) + + assert approved is True + assert result == "Design output" + + def test_execute_workflow_not_implemented(self): + """Test that execute_workflow raises NotImplementedError in base class.""" + manager = ManagerAgent(crews={"test": "test_crew"}) + + with pytest.raises(NotImplementedError, match="Subclasses must implement"): + asyncio.run(manager.execute_workflow("test task")) + + +class TestManagerAgentSubclass: + """Tests for ManagerAgent subclass implementation.""" + + @pytest.mark.asyncio + async def test_custom_workflow_implementation(self): + """Test a custom manager implementation.""" + + class TestManager(ManagerAgent): + async def execute_workflow(self, task: str, **kwargs): + # Simple sequential workflow + design = await self.delegate_async("design", task) + return await self.delegate_async("impl", design) + + manager = TestManager( + crews={"design": "game_design", "impl": "implementation"}, + package_name="test_pkg", + ) + + mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_config = {"name": "test", "agents": {}, "tasks": {}} + + with ( + patch("agentic_fabric.core.manager.discover_packages") as mock_discover, + patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + ): + mock_discover.return_value = mock_packages + mock_get_config.return_value = mock_config + mock_run.side_effect = ["Design done", "Implementation done"] + + result = await manager.execute_workflow("Build a game") + + assert result == "Implementation done" + assert mock_run.call_count == 2 + + @pytest.mark.asyncio + async def test_custom_workflow_with_parallel_execution(self): + """Test a custom manager with parallel execution.""" + + class ParallelManager(ManagerAgent): + async def execute_workflow(self, task: str, **kwargs): + # Parallel execution + results = await self.delegate_parallel( + [ + ("design", task), + ("assets", task), + ] + ) + # Then sequential QA + return await self.delegate_async( + "qa", + { + "design": results[0], + "assets": results[1], + }, + ) + + manager = ParallelManager( + crews={"design": "design", "assets": "assets", "qa": "qa"}, + package_name="test_pkg", + ) + + mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_config = {"name": "test", "agents": {}, "tasks": {}} + + with ( + patch("agentic_fabric.core.manager.discover_packages") as mock_discover, + patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + ): + mock_discover.return_value = mock_packages + mock_get_config.return_value = mock_config + mock_run.side_effect = ["Design done", "Assets done", "QA passed"] + + result = await manager.execute_workflow("Build game") + + assert result == "QA passed" + assert mock_run.call_count == 3 diff --git a/packages/agentic-fabric/tests/test_runners.py b/packages/agentic-fabric/tests/test_runners.py new file mode 100644 index 0000000..b4bc5a1 --- /dev/null +++ b/packages/agentic-fabric/tests/test_runners.py @@ -0,0 +1,858 @@ +"""Tests for runner implementations. + +These tests verify the interface contracts for all runner implementations +without requiring the actual frameworks to be installed. +""" + +from __future__ import annotations + +import sys + +from typing import TYPE_CHECKING, Any + + +if TYPE_CHECKING: + from tests._crew_mocker import CrewMocker + + +class TestCrewAIRunner: + """Tests for CrewAI runner implementation.""" + + def test_build_agent_creates_crewai_agent(self, crew_mocker: CrewMocker) -> None: + """Test that build_agent creates a CrewAI Agent.""" + crew_mocker.mock_crewai() + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + MockAgent = crew_mocker.patch_crewai_agent() + crew_mocker.patch_get_llm() + + runner = CrewAIRunner() + + agent_config = { + "role": "Test Agent", + "goal": "Test goal", + "backstory": "Test backstory", + "allow_delegation": True, + } + + tools = [crew_mocker.MagicMock()] + runner.build_agent(agent_config, tools=tools) + + MockAgent.assert_called_once() + call_kwargs = MockAgent.call_args[1] + assert call_kwargs["role"] == "Test Agent" + assert call_kwargs["goal"] == "Test goal" + assert call_kwargs["backstory"] == "Test backstory" + assert call_kwargs["allow_delegation"] is True + assert call_kwargs["tools"] == tools + + def test_build_task_creates_crewai_task(self, crew_mocker: CrewMocker) -> None: + """Test that build_task creates a CrewAI Task.""" + crew_mocker.mock_crewai() + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + MockTask = crew_mocker.patch_crewai_task() + + runner = CrewAIRunner() + + task_config = { + "description": "Test task description", + "expected_output": "Test output", + } + mock_agent = crew_mocker.MagicMock() + + runner.build_task(task_config, mock_agent) + + MockTask.assert_called_once() + call_kwargs = MockTask.call_args[1] + assert call_kwargs["description"] == "Test task description" + assert call_kwargs["expected_output"] == "Test output" + assert call_kwargs["agent"] == mock_agent + + def test_build_task_includes_context(self, crew_mocker: CrewMocker) -> None: + """Test that build_task includes context tasks when provided.""" + crew_mocker.mock_crewai() + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + MockTask = crew_mocker.patch_crewai_task() + + runner = CrewAIRunner() + + task_config = { + "description": "Test task", + "expected_output": "Test output", + } + mock_agent = crew_mocker.MagicMock() + context_tasks = [crew_mocker.MagicMock(), crew_mocker.MagicMock()] + + runner.build_task(task_config, mock_agent, context=context_tasks) + + call_kwargs = MockTask.call_args[1] + assert call_kwargs["context"] == context_tasks + + def test_build_crew_creates_crewai_crew(self, crew_mocker: CrewMocker) -> None: + """Test that build_crew creates a CrewAI Crew.""" + crew_mocker.mock_crewai() + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + MockCrew = crew_mocker.patch_crewai_crew() + MockAgent = crew_mocker.patch_crewai_agent() + MockTask = crew_mocker.patch_crewai_task() + crew_mocker.patch_crewai_process() + crew_mocker.patch_get_llm() + + runner = CrewAIRunner() + + mock_agent = crew_mocker.MagicMock() + MockAgent.return_value = mock_agent + + mock_task = crew_mocker.MagicMock() + MockTask.return_value = mock_task + + crew_config = { + "name": "test_crew", + "agents": { + "agent1": { + "role": "Agent 1", + "goal": "Goal 1", + "backstory": "Backstory 1", + } + }, + "tasks": { + "task1": { + "description": "Task 1", + "expected_output": "Output 1", + "agent": "agent1", + } + }, + "knowledge_paths": [], + } + + runner.build_crew(crew_config) + + MockCrew.assert_called_once() + call_kwargs = MockCrew.call_args[1] + assert len(call_kwargs["agents"]) == 1 + assert len(call_kwargs["tasks"]) == 1 + assert call_kwargs["planning"] is True + assert call_kwargs["memory"] is True + + def test_run_executes_crew_and_returns_result(self, crew_mocker: CrewMocker) -> None: + """Test that run executes crew and returns string result.""" + crew_mocker.mock_crewai() + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + runner = CrewAIRunner() + + mock_crew = crew_mocker.mock_crewai_crew(result="Test output") + + result = runner.run(mock_crew, {"input": "test input"}) + + mock_crew.kickoff.assert_called_once_with(inputs={"input": "test input"}) + assert result == "Test output" + + def test_run_handles_result_without_raw(self, crew_mocker: CrewMocker) -> None: + """Test that run handles results without raw attribute.""" + crew_mocker.mock_crewai() + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + runner = CrewAIRunner() + + mock_crew = crew_mocker.MagicMock() + mock_crew.kickoff.return_value = "Direct string result" + + result = runner.run(mock_crew, {}) + + assert result == "Direct string result" + + def test_handles_knowledge_sources(self, crew_mocker: CrewMocker, tmp_path) -> None: + """Test that knowledge sources are loaded from paths.""" + crew_mocker.mock_crewai() + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + MockKnowledge = crew_mocker.patch_knowledge_source() + + runner = CrewAIRunner() + + # Create test knowledge file + knowledge_dir = tmp_path / "knowledge" + knowledge_dir.mkdir() + (knowledge_dir / "test.md").write_text("# Test Knowledge\nContent here") + + sources = runner._load_knowledge([knowledge_dir]) + + # Verify TextFileKnowledgeSource was called for the .md file + assert MockKnowledge.called + assert len(sources) > 0 + + def test_handles_task_context_dependencies(self, crew_mocker: CrewMocker) -> None: + """Test that tasks with context dependencies are handled correctly.""" + crew_mocker.mock_crewai() + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + crew_mocker.patch_crewai_crew() + MockAgent = crew_mocker.patch_crewai_agent() + MockTask = crew_mocker.patch_crewai_task() + crew_mocker.patch_crewai_process() + crew_mocker.patch_get_llm() + + runner = CrewAIRunner() + + mock_agent = crew_mocker.MagicMock() + MockAgent.return_value = mock_agent + + mock_task1 = crew_mocker.MagicMock() + mock_task2 = crew_mocker.MagicMock() + MockTask.side_effect = [mock_task1, mock_task2] + + crew_config = { + "agents": { + "agent1": { + "role": "Agent", + "goal": "Goal", + "backstory": "Story", + } + }, + "tasks": { + "task1": { + "description": "Task 1", + "expected_output": "Output 1", + "agent": "agent1", + }, + "task2": { + "description": "Task 2", + "expected_output": "Output 2", + "agent": "agent1", + "context": ["task1"], + }, + }, + "knowledge_paths": [], + } + + runner.build_crew(crew_config) + + # Verify second task was created with context + assert MockTask.call_count == 2 + second_task_kwargs = MockTask.call_args_list[1][1] + assert "context" in second_task_kwargs + assert second_task_kwargs["context"] == [mock_task1] + + def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> None: + """Tool names declared in agent config should be instantiated and attached.""" + crew_mocker.mock_crewai() + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + crew_mocker.patch_crewai_crew() + MockAgent = crew_mocker.patch_crewai_agent() + crew_mocker.patch_crewai_task() + crew_mocker.patch_crewai_process() + crew_mocker.patch_get_llm() + + resolved_tool = crew_mocker.MagicMock() + + runner = CrewAIRunner() + runner._resolve_tools = crew_mocker.MagicMock(return_value=[resolved_tool]) + + crew_config = { + "agents": { + "agent1": { + "role": "Agent 1", + "goal": "Goal 1", + "backstory": "Backstory 1", + "tools": ["FileWriteTool"], + } + }, + "tasks": { + "task1": { + "description": "Task 1", + "expected_output": "Output 1", + "agent": "agent1", + } + }, + "knowledge_paths": [], + } + + runner.build_crew(crew_config) + + runner._resolve_tools.assert_called_once_with(["FileWriteTool"]) + assert MockAgent.call_args[1]["tools"] == [resolved_tool] + + +class TestLangGraphRunner: + """Tests for LangGraph runner implementation.""" + + def test_build_agent_creates_react_agent(self, crew_mocker: CrewMocker) -> None: + """Test that build_agent creates a LangGraph ReAct agent.""" + crew_mocker.mock_langgraph() + + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + mock_create = crew_mocker.patch_create_react_agent() + MockLLM = crew_mocker.patch_chat_anthropic() + mock_llm = crew_mocker.MagicMock() + MockLLM.return_value = mock_llm + + runner = LangGraphRunner() + + agent_config = { + "role": "Test Agent", + "goal": "Test goal", + "llm": "claude-sonnet-4-20250514", + } + tools = [crew_mocker.MagicMock()] + + runner.build_agent(agent_config, tools=tools) + + mock_create.assert_called_once() + args = mock_create.call_args[0] + assert args[0] == mock_llm + assert args[1] == tools + + def test_build_crew_creates_graph(self, crew_mocker: CrewMocker) -> None: + """Test that build_crew creates a LangGraph workflow.""" + crew_mocker.mock_langgraph() + + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + mock_create = crew_mocker.patch_create_react_agent() + crew_mocker.patch_chat_anthropic() + + runner = LangGraphRunner() + + crew_config = { + "llm": {"model": "claude-sonnet-4-20250514"}, + "agents": {}, + "tasks": {}, + } + + runner.build_crew(crew_config) + + mock_create.assert_called_once() + + def test_run_invokes_graph(self, crew_mocker: CrewMocker) -> None: + """Test that run invokes the LangGraph workflow.""" + crew_mocker.mock_langgraph() + + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + runner = LangGraphRunner() + + mock_graph = crew_mocker.mock_langgraph_graph(result="Test response") + + result = runner.run(mock_graph, {"input": "test prompt"}) + + mock_graph.invoke.assert_called_once() + invoke_args = mock_graph.invoke.call_args[0][0] + assert "messages" in invoke_args + assert result == "Test response" + + def test_run_handles_dict_messages(self, crew_mocker: CrewMocker) -> None: + """Test that run handles dictionary message format.""" + crew_mocker.mock_langgraph() + + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + runner = LangGraphRunner() + + mock_graph = crew_mocker.MagicMock() + mock_graph.invoke.return_value = {"messages": [("assistant", "Test response")]} + + runner.run(mock_graph, {"task": "test task"}) + + mock_graph.invoke.assert_called_once() + invoke_args = mock_graph.invoke.call_args[0][0] + assert "messages" in invoke_args + assert any("test task" in str(msg) for msg in invoke_args["messages"]) + + def test_get_llm_returns_chat_anthropic(self, crew_mocker: CrewMocker) -> None: + """Test that get_llm returns ChatAnthropic instance.""" + crew_mocker.mock_langgraph() + + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + MockLLM = crew_mocker.patch_chat_anthropic() + + runner = LangGraphRunner() + runner.get_llm("claude-sonnet-4-20250514") + + MockLLM.assert_called_once_with(model="claude-sonnet-4-20250514") + + def test_get_llm_uses_default_model(self, crew_mocker: CrewMocker) -> None: + """Test that get_llm uses default model when none specified.""" + crew_mocker.mock_langgraph() + + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + MockLLM = crew_mocker.patch_chat_anthropic() + + runner = LangGraphRunner() + runner.get_llm() + + MockLLM.assert_called_once() + assert MockLLM.call_args[1]["model"] == "claude-haiku-4-5-20251001" + + def test_build_task_returns_dict(self, crew_mocker: CrewMocker) -> None: + """Test that build_task returns task configuration dict.""" + crew_mocker.mock_langgraph() + + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + runner = LangGraphRunner() + + task_config = { + "description": "Test task", + "expected_output": "Test output", + } + mock_agent = crew_mocker.MagicMock() + + result = runner.build_task(task_config, mock_agent) + + assert isinstance(result, dict) + assert result["description"] == "Test task" + assert result["expected_output"] == "Test output" + assert result["agent"] == mock_agent + + def test_handles_multi_agent_flows(self, crew_mocker: CrewMocker) -> None: + """Test that multiple agents can be created for complex flows.""" + crew_mocker.mock_langgraph() + + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + mock_create = crew_mocker.patch_create_react_agent() + crew_mocker.patch_chat_anthropic() + + runner = LangGraphRunner() + + # Create multiple agents + agent1_config = {"role": "Agent 1", "goal": "Goal 1"} + agent2_config = {"role": "Agent 2", "goal": "Goal 2"} + + runner.build_agent(agent1_config) + runner.build_agent(agent2_config) + + # Verify create_react_agent was called twice + assert mock_create.call_count == 2 + + def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> None: + """Configured agent tools should be adapted for LangGraph crews.""" + crew_mocker.mock_langgraph() + + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + mock_create = crew_mocker.patch_create_react_agent() + crew_mocker.patch_chat_anthropic() + resolved_tool = crew_mocker.MagicMock() + crew_mocker.patch("agentic_fabric.runners.langgraph_runner.resolve_langgraph_tools", return_value=[resolved_tool]) + + runner = LangGraphRunner() + crew_config = { + "llm": {"model": "claude-sonnet-4-20250514"}, + "agents": { + "researcher": { + "tools": ["ScrapeWebsiteTool"], + } + }, + "tasks": {}, + } + + runner.build_crew(crew_config) + + mock_create.assert_called_once() + assert mock_create.call_args[0][1] == [resolved_tool] + + +class TestStrandsRunner: + """Tests for Strands runner implementation.""" + + def test_build_agent_creates_strands_agent(self, crew_mocker: CrewMocker) -> None: + """Test that build_agent creates a Strands Agent.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + MockAgent = crew_mocker.patch_strands_agent() + + runner = StrandsRunner() + + agent_config = { + "role": "Test Agent", + "goal": "Test goal", + "backstory": "Test backstory", + } + tools = [crew_mocker.MagicMock()] + + runner.build_agent(agent_config, tools=tools) + + MockAgent.assert_called_once() + call_kwargs = MockAgent.call_args[1] + assert "Test Agent" in call_kwargs["system_prompt"] + assert "Test goal" in call_kwargs["system_prompt"] + assert "Test backstory" in call_kwargs["system_prompt"] + assert call_kwargs["tools"] == tools + + def test_build_system_prompt_combines_agent_prompts(self, crew_mocker: CrewMocker) -> None: + """Test that _build_system_prompt combines multiple agent roles.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + crew_config = { + "description": "Test crew description", + "agents": { + "agent1": { + "role": "Agent One", + "goal": "Goal one", + }, + "agent2": { + "role": "Agent Two", + "goal": "Goal two", + }, + }, + "tasks": { + "task1": { + "description": "Task one description", + } + }, + } + + prompt = runner._build_system_prompt(crew_config) + + assert "Test crew description" in prompt + assert "Agent One" in prompt + assert "Agent Two" in prompt + assert "Goal one" in prompt + assert "Goal two" in prompt + assert "task1" in prompt + + def test_build_system_prompt_truncates_long_descriptions(self, crew_mocker: CrewMocker) -> None: + """Test that long task descriptions are truncated.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + long_desc = "A" * 250 + crew_config = { + "tasks": { + "task1": { + "description": long_desc, + } + }, + } + + prompt = runner._build_system_prompt(crew_config) + + # Should be truncated to 200 chars + ellipsis + assert "..." in prompt + assert prompt.count("A") == 200 + + def test_build_system_prompt_keeps_short_descriptions(self, crew_mocker: CrewMocker) -> None: + """Test that short task descriptions are not truncated.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + short_desc = "Short description" + crew_config = { + "tasks": { + "task1": { + "description": short_desc, + } + }, + } + + prompt = runner._build_system_prompt(crew_config) + + # Should not have ellipsis for short descriptions + assert "..." not in prompt + assert short_desc in prompt + + def test_run_returns_string_result(self, crew_mocker: CrewMocker) -> None: + """Test that run executes agent and returns string result.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + mock_agent = crew_mocker.mock_strands_agent(result="Test agent response") + + result = runner.run(mock_agent, {"input": "test prompt"}) + + mock_agent.assert_called_once_with("test prompt") + assert result == "Test agent response" + + def test_run_handles_task_input(self, crew_mocker: CrewMocker) -> None: + """Test that run handles 'task' key in inputs.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + mock_agent = crew_mocker.mock_strands_agent(result="Response") + + runner.run(mock_agent, {"task": "do something"}) + + mock_agent.assert_called_once_with("do something") + + def test_get_model_provider_from_string(self, crew_mocker: CrewMocker) -> None: + """Test extracting model provider from string config.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + result = runner._get_model_provider("claude-3-5-sonnet") + + assert result == "claude-3-5-sonnet" + + def test_get_model_provider_from_dict(self, crew_mocker: CrewMocker) -> None: + """Test extracting model provider from dict config.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + result = runner._get_model_provider({"model": "claude-3-5-sonnet", "provider": "anthropic"}) + + assert result == "claude-3-5-sonnet" + + def test_get_model_provider_returns_none_for_empty(self, crew_mocker: CrewMocker) -> None: + """Test that None is returned when no config provided.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + result = runner._get_model_provider(None) + + assert result is None + + def test_build_crew_creates_strands_agent(self, crew_mocker: CrewMocker) -> None: + """Test that build_crew creates a Strands agent.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + MockAgent = crew_mocker.patch_strands_agent() + + runner = StrandsRunner() + + crew_config = { + "description": "Test crew", + "llm": {"model": "claude-3-5-sonnet"}, + "agents": {}, + "tasks": {}, + } + + runner.build_crew(crew_config) + + MockAgent.assert_called_once() + call_kwargs = MockAgent.call_args[1] + assert "system_prompt" in call_kwargs + assert call_kwargs["model_id"] == "claude-3-5-sonnet" + + def test_build_task_returns_dict(self, crew_mocker: CrewMocker) -> None: + """Test that build_task returns task configuration dict.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + runner = StrandsRunner() + + task_config = { + "description": "Test task", + "expected_output": "Test output", + } + mock_agent = crew_mocker.MagicMock() + + result = runner.build_task(task_config, mock_agent) + + assert isinstance(result, dict) + assert result["description"] == "Test task" + assert result["expected_output"] == "Test output" + assert result["agent"] == mock_agent + + def test_combines_agent_prompts(self, crew_mocker: CrewMocker) -> None: + """Test that multiple agent capabilities are combined into system prompt.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + MockAgent = crew_mocker.patch_strands_agent() + + runner = StrandsRunner() + + crew_config = { + "description": "Multi-capability crew", + "agents": { + "researcher": {"role": "Researcher", "goal": "Research topics"}, + "writer": {"role": "Writer", "goal": "Write content"}, + }, + "tasks": {}, + } + + runner.build_crew(crew_config) + + call_kwargs = MockAgent.call_args[1] + system_prompt = call_kwargs["system_prompt"] + + # Verify both agent capabilities are in the prompt + assert "Researcher" in system_prompt + assert "Writer" in system_prompt + assert "Research topics" in system_prompt + assert "Write content" in system_prompt + + def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> None: + """Configured agent tools should be adapted for Strands crews.""" + crew_mocker.mock_strands() + + from agentic_fabric.runners.strands_runner import StrandsRunner + + MockAgent = crew_mocker.patch_strands_agent() + resolved_tool = crew_mocker.MagicMock() + crew_mocker.patch("agentic_fabric.runners.strands_runner.resolve_strands_tools", return_value=[resolved_tool]) + + runner = StrandsRunner() + crew_config = { + "description": "Test crew", + "agents": { + "writer": { + "tools": ["FileWriteTool"], + } + }, + "tasks": {}, + } + + runner.build_crew(crew_config) + + assert MockAgent.call_args[1]["tools"] == [resolved_tool] + + +class TestBaseRunner: + """Tests for base runner interface.""" + + def test_build_and_run_convenience_method(self, crew_mocker: CrewMocker) -> None: + """Test that build_and_run calls build_crew and run with correct args.""" + from agentic_fabric.runners.base import BaseRunner + + class TestRunner(BaseRunner): + # Define abstract methods so the class can be instantiated + def build_crew(self, crew_config: dict[str, Any]) -> Any: + pass + + def run(self, crew: Any, inputs: dict[str, Any]) -> str: + pass + + def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: + return crew_mocker.MagicMock() + + def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: + return crew_mocker.MagicMock() + + runner = TestRunner() + + # Mock the methods to verify they're called correctly + mock_crew_obj = crew_mocker.MagicMock() + runner.build_crew = crew_mocker.MagicMock(return_value=mock_crew_obj) + runner.run = crew_mocker.MagicMock(return_value="test result") + + crew_config = {"test": "config"} + inputs = {"test": "input"} + + result = runner.build_and_run(crew_config, inputs) + + assert result == "test result" + runner.build_crew.assert_called_once_with(crew_config) + runner.run.assert_called_once_with(mock_crew_obj, inputs) + + def test_build_and_run_uses_empty_inputs_when_none(self, crew_mocker: CrewMocker) -> None: + """Test that build_and_run uses empty dict when inputs is None.""" + from agentic_fabric.runners.base import BaseRunner + + class TestRunner(BaseRunner): + def build_crew(self, crew_config: dict[str, Any]) -> Any: + return crew_mocker.MagicMock() + + def run(self, crew: Any, inputs: dict[str, Any]) -> str: + assert inputs == {} + return "result" + + def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: + return crew_mocker.MagicMock() + + def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: + return crew_mocker.MagicMock() + + runner = TestRunner() + result = runner.build_and_run({}, None) + + assert result == "result" + + def test_get_llm_default_implementation(self, crew_mocker: CrewMocker) -> None: + """Test that get_llm has a default implementation.""" + mock_get_llm = crew_mocker.patch_get_llm() + crew_mocker.patch("agentic_fabric.config.llm.DEFAULT_MODEL", "claude-sonnet-4-20250514") + + from agentic_fabric.runners.base import BaseRunner + + class TestRunner(BaseRunner): + def build_crew(self, crew_config: dict[str, Any]) -> Any: + return crew_mocker.MagicMock() + + def run(self, crew: Any, inputs: dict[str, Any]) -> str: + return "result" + + def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: + return crew_mocker.MagicMock() + + def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: + return crew_mocker.MagicMock() + + runner = TestRunner() + llm = runner.get_llm() + + assert llm == mock_get_llm.return_value + + def test_get_llm_returns_none_when_module_unavailable(self, crew_mocker: CrewMocker) -> None: + """Test that get_llm returns None when llm module not available.""" + from agentic_fabric.runners.base import BaseRunner + + class TestRunner(BaseRunner): + def build_crew(self, crew_config: dict[str, Any]) -> Any: + return crew_mocker.MagicMock() + + def run(self, crew: Any, inputs: dict[str, Any]) -> str: + return "result" + + def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: + return crew_mocker.MagicMock() + + def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: + return crew_mocker.MagicMock() + + runner = TestRunner() + + # Simulate that the agentic_fabric.config.llm module is not available + crew_mocker.patch.dict(sys.modules, {"agentic_fabric.config.llm": None}) + llm = runner.get_llm() + + assert llm is None diff --git a/packages/agentic-fabric/tests/test_scraping_tools.py b/packages/agentic-fabric/tests/test_scraping_tools.py new file mode 100644 index 0000000..2b2daea --- /dev/null +++ b/packages/agentic-fabric/tests/test_scraping_tools.py @@ -0,0 +1,34 @@ +"""Tests for the scraping tools.""" + +from __future__ import annotations + +from unittest.mock import MagicMock, patch + +import pytest + + +pytest.importorskip("crewai", reason="crewai not installed") + +from agentic_fabric.tools.scraping_tools import CrawlWebsiteTool + + +@patch("agentic_fabric.tools.scraping_tools.requests.get") +def test_crawl_website_tool(mock_get: MagicMock): + """Tests that the CrawlWebsiteTool scrapes content and discovers links.""" + mock_response_page1 = MagicMock() + mock_response_page1.content = b'Page 2

Content 1

' + mock_response_page1.raise_for_status.return_value = None + + mock_response_page2 = MagicMock() + mock_response_page2.content = b"

Content 2

" + mock_response_page2.raise_for_status.return_value = None + + mock_get.side_effect = [mock_response_page1, mock_response_page2] + + tool = CrawlWebsiteTool() + result = tool._run("http://example.com") + + assert "Content 1" in result + assert "Page 2" in result + assert "Content 2" in result + assert mock_get.call_count == 2 diff --git a/packages/agentic-fabric/tests/test_single_agent_runners.py b/packages/agentic-fabric/tests/test_single_agent_runners.py new file mode 100644 index 0000000..145a78a --- /dev/null +++ b/packages/agentic-fabric/tests/test_single_agent_runners.py @@ -0,0 +1,416 @@ +"""Tests for single-agent CLI runners.""" + +from __future__ import annotations + +import subprocess + +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from agentic_fabric.runners.local_cli_runner import LocalCLIConfig, LocalCLIRunner +from agentic_fabric.runners.single_agent_runner import SingleAgentRunner + + +class TestSingleAgentRunner: + """Test the SingleAgentRunner base class.""" + + def test_is_abstract(self): + """SingleAgentRunner should be abstract.""" + with pytest.raises(TypeError): + SingleAgentRunner() + + def test_default_is_available(self): + """Default is_available should return True.""" + + class TestRunner(SingleAgentRunner): + def run(self, task: str, working_dir: str | None = None, **kwargs): + return "test" + + runner = TestRunner() + assert runner.is_available() is True + + def test_default_get_required_env_vars(self): + """Default get_required_env_vars should return empty list.""" + + class TestRunner(SingleAgentRunner): + def run(self, task: str, working_dir: str | None = None, **kwargs): + return "test" + + runner = TestRunner() + assert runner.get_required_env_vars() == [] + + +class TestLocalCLIConfig: + """Test the LocalCLIConfig dataclass.""" + + def test_minimal_config(self): + """Should create config with minimal required fields.""" + config = LocalCLIConfig( + command="test-tool", + task_flag="--task", + ) + + assert config.command == "test-tool" + assert config.task_flag == "--task" + assert config.auth_env == [] + assert config.timeout == 300 + + def test_full_config(self): + """Should create config with all fields.""" + config = LocalCLIConfig( + command="aider", + task_flag="--message", + subcommand=None, + auth_env=["OPENAI_API_KEY"], + auto_approve="--yes-always", + structured_output="--json", + model_flag="--model", + default_model="gpt-4o", + working_dir_flag="--cwd", + additional_flags=["--no-git"], + timeout=600, + name="Aider", + description="AI pair programming", + install_cmd="pipx install aider-chat", + ) + + assert config.command == "aider" + assert config.auth_env == ["OPENAI_API_KEY"] + assert config.auto_approve == "--yes-always" + assert config.timeout == 600 + + +class TestLocalCLIRunner: + """Test the LocalCLIRunner implementation.""" + + @pytest.fixture + def mock_profiles_file(self, tmp_path: Path): + """Create a temporary profiles YAML file.""" + profiles_content = """ +profiles: + test-tool: + name: "Test Tool" + description: "A test tool" + command: "test-tool" + task_flag: "--task" + auth_env: + - "TEST_API_KEY" + auto_approve: "--yes" + timeout: 120 + install_cmd: "pip install test-tool" + + aider: + name: "Aider" + description: "AI pair programming" + command: "aider" + task_flag: "--message" + auth_env: + - "OPENAI_API_KEY" + auto_approve: "--yes-always" + model_flag: "--model" + additional_flags: + - "--no-git" + timeout: 300 + install_cmd: "pipx install aider-chat" +""" + profiles_file = tmp_path / "local_cli_profiles.yaml" + profiles_file.write_text(profiles_content) + return profiles_file + + def test_load_profiles(self, mock_profiles_file: Path): + """Should load profiles from YAML file.""" + with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: + mock_path.return_value.parent = mock_profiles_file.parent + mock_path.return_value.__truediv__.return_value = mock_profiles_file + + # Clear cache + LocalCLIRunner._profiles_cache = None + + profiles = LocalCLIRunner._load_profiles() + + assert "test-tool" in profiles + assert "aider" in profiles + assert profiles["test-tool"].command == "test-tool" + assert profiles["aider"].auth_env == ["OPENAI_API_KEY"] + + def test_init_with_profile_name(self, mock_profiles_file: Path): + """Should initialize with a profile name.""" + with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: + mock_path.return_value.parent = mock_profiles_file.parent + mock_path.return_value.__truediv__.return_value = mock_profiles_file + + LocalCLIRunner._profiles_cache = None + + runner = LocalCLIRunner("aider") + + assert runner.config.command == "aider" + assert runner.config.task_flag == "--message" + + def test_init_with_unknown_profile(self, mock_profiles_file: Path): + """Should raise ValueError for unknown profile.""" + with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: + mock_path.return_value.parent = mock_profiles_file.parent + mock_path.return_value.__truediv__.return_value = mock_profiles_file + + LocalCLIRunner._profiles_cache = None + + with pytest.raises(ValueError, match="Unknown profile"): + LocalCLIRunner("unknown-tool") + + def test_init_with_config_dict(self): + """Should initialize with a config dict.""" + config_dict = { + "command": "my-tool", + "task_flag": "--prompt", + "auto_approve": "--yes", + } + + runner = LocalCLIRunner(config_dict) + + assert runner.config.command == "my-tool" + assert runner.config.task_flag == "--prompt" + assert runner.config.auto_approve == "--yes" + + def test_init_with_config_object(self): + """Should initialize with a LocalCLIConfig object.""" + config = LocalCLIConfig( + command="custom-tool", + task_flag="--task", + ) + + runner = LocalCLIRunner(config) + + assert runner.config.command == "custom-tool" + assert runner.config.task_flag == "--task" + + def test_is_available_tool_exists(self): + """Should return True if tool is in PATH.""" + config = LocalCLIConfig(command="python", task_flag="--task") + runner = LocalCLIRunner(config) + + assert runner.is_available() is True + + def test_is_available_tool_missing(self): + """Should return False if tool is not in PATH.""" + config = LocalCLIConfig(command="nonexistent-tool-xyz", task_flag="--task") + runner = LocalCLIRunner(config) + + assert runner.is_available() is False + + def test_get_required_env_vars(self): + """Should return required env vars from config.""" + config = LocalCLIConfig( + command="test", + task_flag="--task", + auth_env=["API_KEY", "SECRET"], + ) + runner = LocalCLIRunner(config) + + assert runner.get_required_env_vars() == ["API_KEY", "SECRET"] + + def test_run_missing_env_vars(self): + """Should raise RuntimeError if required env vars missing.""" + config = LocalCLIConfig( + command="test", + task_flag="--task", + auth_env=["MISSING_ENV_VAR"], + ) + runner = LocalCLIRunner(config) + + with pytest.raises(RuntimeError, match="Missing required environment variables"): + runner.run("test task") + + @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) + @patch("subprocess.run") + def test_run_success(self, mock_run: MagicMock): + """Should execute command successfully.""" + config = LocalCLIConfig( + command="test-tool", + task_flag="--task", + auth_env=["TEST_API_KEY"], + ) + runner = LocalCLIRunner(config) + + # Mock successful execution + mock_run.return_value = MagicMock( + returncode=0, + stdout="Test output", + stderr="", + ) + + result = runner.run("Fix the bug") + + assert result == "Test output" + mock_run.assert_called_once() + + # Check command was built correctly + call_args = mock_run.call_args + assert call_args[0][0] == ["test-tool", "--task", "Fix the bug"] + + @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) + @patch("subprocess.run") + def test_run_with_auto_approve(self, mock_run: MagicMock): + """Should include auto-approve flag when enabled.""" + config = LocalCLIConfig( + command="test-tool", + task_flag="--task", + auth_env=["TEST_API_KEY"], + auto_approve="--yes", + ) + runner = LocalCLIRunner(config) + + mock_run.return_value = MagicMock(returncode=0, stdout="Output", stderr="") + + runner.run("Test task", auto_approve=True) + + call_args = mock_run.call_args + assert "--yes" in call_args[0][0] + + @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) + @patch("subprocess.run") + def test_run_with_structured_output(self, mock_run: MagicMock): + """Should include structured output flag when enabled.""" + config = LocalCLIConfig( + command="test-tool", + task_flag="--task", + auth_env=["TEST_API_KEY"], + structured_output="--json", + ) + runner = LocalCLIRunner(config) + + mock_run.return_value = MagicMock(returncode=0, stdout="Output", stderr="") + + runner.run("Test task", structured_output=True) + + call_args = mock_run.call_args + assert "--json" in call_args[0][0] + + @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) + @patch("subprocess.run") + def test_run_with_model(self, mock_run: MagicMock): + """Should include model flag when model specified.""" + config = LocalCLIConfig( + command="test-tool", + task_flag="--task", + auth_env=["TEST_API_KEY"], + model_flag="--model", + ) + runner = LocalCLIRunner(config) + + mock_run.return_value = MagicMock(returncode=0, stdout="Output", stderr="") + + runner.run("Test task", model="gpt-4o") + + call_args = mock_run.call_args + cmd = call_args[0][0] + assert "--model" in cmd + assert "gpt-4o" in cmd + + @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) + @patch("subprocess.run") + def test_run_with_additional_flags(self, mock_run: MagicMock): + """Should include additional flags from config.""" + config = LocalCLIConfig( + command="test-tool", + task_flag="--task", + auth_env=["TEST_API_KEY"], + additional_flags=["--no-cache", "--verbose"], + ) + runner = LocalCLIRunner(config) + + mock_run.return_value = MagicMock(returncode=0, stdout="Output", stderr="") + + runner.run("Test task") + + call_args = mock_run.call_args + cmd = call_args[0][0] + assert "--no-cache" in cmd + assert "--verbose" in cmd + + @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) + @patch("subprocess.run") + def test_run_failure(self, mock_run: MagicMock): + """Should raise RuntimeError on command failure.""" + config = LocalCLIConfig( + command="test-tool", + task_flag="--task", + auth_env=["TEST_API_KEY"], + ) + runner = LocalCLIRunner(config) + + # Mock failed execution with CalledProcessError (raised when check=True) + mock_run.side_effect = subprocess.CalledProcessError( + returncode=1, + cmd=["test-tool", "--task", "Test task"], + output="", + stderr="Error occurred", + ) + + with pytest.raises(RuntimeError, match="Command failed with exit code 1"): + runner.run("Test task") + + @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) + @patch("subprocess.run") + def test_run_timeout(self, mock_run: MagicMock): + """Should raise RuntimeError on timeout.""" + config = LocalCLIConfig( + command="test-tool", + task_flag="--task", + auth_env=["TEST_API_KEY"], + timeout=1, + ) + runner = LocalCLIRunner(config) + + # Mock timeout + mock_run.side_effect = subprocess.TimeoutExpired("test-tool", 1) + + with pytest.raises(RuntimeError, match="Command timed out"): + runner.run("Test task") + + @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) + @patch("subprocess.run") + def test_build_command_positional_task(self, mock_run: MagicMock): + """Should handle positional task argument (no flag).""" + # Note: command can contain spaces and will be split with shlex + # But using subcommand/default_model is more explicit (see next test) + config = LocalCLIConfig( + command="test-tool", + task_flag="", # Positional + auth_env=["TEST_API_KEY"], + ) + runner = LocalCLIRunner(config) + + mock_run.return_value = MagicMock(returncode=0, stdout="Output", stderr="") + + runner.run("Fix the bug") + + call_args = mock_run.call_args + cmd = call_args[0][0] + # Should be: ["test-tool", "Fix the bug"] + assert cmd == ["test-tool", "Fix the bug"] + assert "--task" not in cmd + + @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) + @patch("subprocess.run") + def test_build_command_with_subcommand(self, mock_run: MagicMock): + """Should include subcommand if specified.""" + config = LocalCLIConfig( + command="ollama", + subcommand="run", + task_flag="", + auth_env=["TEST_API_KEY"], + default_model="codellama", + ) + runner = LocalCLIRunner(config) + + mock_run.return_value = MagicMock(returncode=0, stdout="Output", stderr="") + + runner.run("Test task") + + call_args = mock_run.call_args + cmd = call_args[0][0] + assert cmd[0] == "ollama" + assert cmd[1] == "run" + assert "codellama" in cmd diff --git a/packages/agentic-fabric/tests/test_tool_adapters.py b/packages/agentic-fabric/tests/test_tool_adapters.py new file mode 100644 index 0000000..641f653 --- /dev/null +++ b/packages/agentic-fabric/tests/test_tool_adapters.py @@ -0,0 +1,169 @@ +"""Tests for framework-specific configured tool adapters.""" + +from __future__ import annotations + +import sys + +from types import ModuleType, SimpleNamespace +from unittest.mock import MagicMock, patch + +from pydantic import BaseModel + +from agentic_fabric.tools.adapters import resolve_langgraph_tools, resolve_strands_tools + + +class WriteFileArgs(BaseModel): + """Schema used to verify adapter schema forwarding.""" + + file_path: str + + +class DummyTool: + """Simple tool object with a CrewAI-style `_run` entrypoint.""" + + name = "Write Game Code File" + description = "Write code into the workspace." + args_schema = WriteFileArgs + + def __init__(self) -> None: + self.calls: list[dict[str, str]] = [] + + def _run(self, **kwargs: str) -> str: + self.calls.append(kwargs) + return f"wrote:{kwargs['file_path']}" + + +class FakeStructuredTool: + """Minimal LangChain-style tool wrapper used for adapter tests.""" + + def __init__( + self, + *, + func, + name: str, + description: str, + args_schema: type[BaseModel] | None, + infer_schema: bool, + ) -> None: + self.func = func + self.name = name + self.description = description + self.args_schema = args_schema + self.infer_schema = infer_schema + + @classmethod + def from_function( + cls, + *, + func, + name: str, + description: str, + args_schema: type[BaseModel] | None, + infer_schema: bool, + ) -> FakeStructuredTool: + return cls( + func=func, + name=name, + description=description, + args_schema=args_schema, + infer_schema=infer_schema, + ) + + def invoke(self, kwargs: dict[str, str]) -> str: + return self.func(**kwargs) + + +def fake_strands_tool(*, name: str, description: str, inputSchema: dict | None): + """Minimal Strands decorator used for adapter tests.""" + + def decorator(func): + def wrapped(**kwargs: str) -> str: + return func(**kwargs) + + wrapped.__name__ = func.__name__ + wrapped.__doc__ = func.__doc__ + wrapped.TOOL_SPEC = { + "name": name, + "description": description, + "inputSchema": inputSchema, + } + return wrapped + + return decorator + + +class TestLangGraphToolAdapters: + """Tests for LangGraph configured-tool wrapping.""" + + @patch("agentic_fabric.tools.adapters.resolve_tools") + def test_wraps_resolved_tools_with_structured_tool(self, mock_resolve_tools: MagicMock) -> None: + """Resolved tools should be adapted into LangChain-compatible wrappers.""" + tool = DummyTool() + mock_resolve_tools.return_value = [tool] + + fake_tools_module = ModuleType("langchain_core.tools") + fake_tools_module.StructuredTool = FakeStructuredTool + + with patch.dict(sys.modules, {"langchain_core.tools": fake_tools_module}): + adapted = resolve_langgraph_tools(["FileWriteTool"]) + + assert len(adapted) == 1 + wrapped = adapted[0] + assert wrapped.name == "Write_Game_Code_File" + assert wrapped.description == "Write code into the workspace." + assert wrapped.args_schema is WriteFileArgs + assert wrapped.infer_schema is False + assert wrapped.invoke({"file_path": "src/game.ts"}) == "wrote:src/game.ts" + assert tool.calls == [{"file_path": "src/game.ts"}] + + @patch("agentic_fabric.tools.adapters.resolve_tools") + def test_preserves_existing_langgraph_tool_objects(self, mock_resolve_tools: MagicMock) -> None: + """Existing invoke-capable tools should pass through unchanged.""" + existing_tool = SimpleNamespace(name="get_magic_number", invoke=MagicMock(return_value=42)) + mock_resolve_tools.return_value = [existing_tool] + + fake_tools_module = ModuleType("langchain_core.tools") + fake_tools_module.StructuredTool = FakeStructuredTool + + with patch.dict(sys.modules, {"langchain_core.tools": fake_tools_module}): + adapted = resolve_langgraph_tools(["get_magic_number"]) + + assert adapted == [existing_tool] + + +class TestStrandsToolAdapters: + """Tests for Strands configured-tool wrapping.""" + + @patch("agentic_fabric.tools.adapters.resolve_tools") + def test_wraps_resolved_tools_with_strands_decorator(self, mock_resolve_tools: MagicMock) -> None: + """Resolved tools should be adapted into callable Strands tool wrappers.""" + tool = DummyTool() + mock_resolve_tools.return_value = [tool] + + fake_strands_module = ModuleType("strands") + fake_strands_module.tool = fake_strands_tool + + with patch.dict(sys.modules, {"strands": fake_strands_module}): + adapted = resolve_strands_tools(["FileWriteTool"]) + + assert len(adapted) == 1 + wrapped = adapted[0] + assert wrapped(file_path="src/game.ts") == "wrote:src/game.ts" + assert tool.calls == [{"file_path": "src/game.ts"}] + assert wrapped.TOOL_SPEC["name"] == "Write_Game_Code_File" + assert wrapped.TOOL_SPEC["description"] == "Write code into the workspace." + assert wrapped.TOOL_SPEC["inputSchema"] == WriteFileArgs.model_json_schema() + + @patch("agentic_fabric.tools.adapters.resolve_tools") + def test_preserves_existing_strands_tool_objects(self, mock_resolve_tools: MagicMock) -> None: + """Existing Strands tool objects should pass through unchanged.""" + existing_tool = SimpleNamespace(TOOL_SPEC={"name": "get_secret_number"}) + mock_resolve_tools.return_value = [existing_tool] + + fake_strands_module = ModuleType("strands") + fake_strands_module.tool = fake_strands_tool + + with patch.dict(sys.modules, {"strands": fake_strands_module}): + adapted = resolve_strands_tools(["get_secret_number"]) + + assert adapted == [existing_tool] diff --git a/packages/agentic-fabric/tests/test_tool_registry.py b/packages/agentic-fabric/tests/test_tool_registry.py new file mode 100644 index 0000000..e0f3aaa --- /dev/null +++ b/packages/agentic-fabric/tests/test_tool_registry.py @@ -0,0 +1,96 @@ +"""Tests for configured tool resolution.""" + +from __future__ import annotations + +from unittest.mock import MagicMock, patch + +from agentic_fabric.tools.registry import resolve_tool, resolve_tools +from agentic_fabric.tools.vendor import VendorCapabilityTool + + +class TestResolveTool: + """Tests for resolving configured tool names.""" + + @patch("agentic_fabric.tools.registry.importlib.import_module") + def test_resolves_builtin_alias(self, mock_import_module: MagicMock) -> None: + """FileWriteTool should map to the game code writer implementation.""" + mock_tool_class = MagicMock() + mock_tool_instance = MagicMock() + mock_tool_class.return_value = mock_tool_instance + + mock_module = MagicMock() + mock_module.GameCodeWriterTool = mock_tool_class + mock_import_module.return_value = mock_module + + result = resolve_tool("FileWriteTool") + + mock_import_module.assert_called_once_with("agentic_fabric.tools.file_tools") + mock_tool_class.assert_called_once_with() + assert result is mock_tool_instance + + @patch("agentic_fabric.tools.registry.importlib.import_module") + def test_resolves_fully_qualified_reference(self, mock_import_module: MagicMock) -> None: + """module:attribute references should be resolved dynamically.""" + mock_tool_class = MagicMock() + mock_tool_instance = MagicMock() + mock_tool_class.return_value = mock_tool_instance + + mock_module = MagicMock() + mock_module.CustomTool = mock_tool_class + mock_import_module.return_value = mock_module + + result = resolve_tool("custom.module:CustomTool") + + mock_import_module.assert_called_once_with("custom.module") + mock_tool_class.assert_called_once_with() + assert result is mock_tool_instance + + def test_returns_none_for_unresolved_mcp_tool(self) -> None: + """Unsupported MCP tool references should be skipped cleanly.""" + assert resolve_tool("mcp://git/execute_command") is None + + def test_resolves_vendor_uri_tool(self) -> None: + """Vendor tool URIs should create lazy vendor capability wrappers.""" + tool = resolve_tool("vendor://github/get_file") + + assert isinstance(tool, VendorCapabilityTool) + assert tool.provider == "github" + assert tool.operation == "get_file" + + def test_resolves_vendor_colon_tool(self) -> None: + """Colon-style vendor tool references should also be supported.""" + tool = resolve_tool("vendor:slack:list_messages") + + assert isinstance(tool, VendorCapabilityTool) + assert tool.provider == "slack" + assert tool.operation == "list_messages" + + @patch("agentic_fabric.tools.registry.importlib.import_module") + def test_resolve_tools_deduplicates_aliases(self, mock_import_module: MagicMock) -> None: + """Aliases pointing to the same tool should not instantiate duplicates.""" + mock_tool_class = MagicMock() + mock_tool_class.return_value = MagicMock() + + mock_module = MagicMock() + mock_module.GameCodeReaderTool = mock_tool_class + mock_import_module.return_value = mock_module + + tools = resolve_tools(["FileReadTool", "mcp://filesystem/read_file", "FileReadTool"]) + + assert len(tools) == 1 + mock_tool_class.assert_called_once_with() + + +class TestVendorCapabilityTool: + """Tests for the generic vendor-backed tool wrapper.""" + + def test_runs_operation_through_supplied_data(self) -> None: + """Supplied data facade should receive provider and kwargs.""" + data = MagicMock() + data.call.return_value = {"ok": True} + tool = VendorCapabilityTool("github", "get_file", data=data) + + result = tool(path="README.md") + + assert result == {"ok": True} + data.call.assert_called_once_with("get_file", "github", path="README.md") diff --git a/packages/agentic-fabric/tests/test_yaml_config_errors.py b/packages/agentic-fabric/tests/test_yaml_config_errors.py new file mode 100644 index 0000000..40339bd --- /dev/null +++ b/packages/agentic-fabric/tests/test_yaml_config_errors.py @@ -0,0 +1,236 @@ +"""Tests for YAML configuration parsing error handling.""" + +from __future__ import annotations + +import logging + +from pathlib import Path + +import pytest +import yaml + +from agentic_fabric.core.discovery import ( + get_crew_config, + get_framework_from_config_dir, + load_manifest, +) + + +class TestLoadManifestErrors: + """Test error handling in load_manifest.""" + + def test_missing_manifest_file_raises(self, tmp_path: Path) -> None: + """Should raise FileNotFoundError for missing manifest.""" + with pytest.raises(FileNotFoundError): + load_manifest(tmp_path / "nonexistent") + + def test_empty_manifest_returns_empty_dict(self, tmp_path: Path) -> None: + """Empty YAML file should return empty dict, not None.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text("") + result = load_manifest(tmp_path) + assert result == {} + + def test_manifest_with_only_comments_returns_empty_dict(self, tmp_path: Path) -> None: + """YAML with only comments should return empty dict.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text("# just a comment\n# another comment\n") + result = load_manifest(tmp_path) + assert result == {} + + def test_invalid_yaml_raises_error(self, tmp_path: Path) -> None: + """Badly formed YAML should raise a yaml error.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text(":\n bad:\n - ][invalid yaml") + with pytest.raises(yaml.YAMLError): + load_manifest(tmp_path) + + def test_manifest_with_no_crews_key(self, tmp_path: Path) -> None: + """Manifest without 'crews' key should parse without error.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text("name: test_package\ndescription: A test\n") + result = load_manifest(tmp_path) + assert result.get("name") == "test_package" + assert result.get("crews") is None + + +class TestGetCrewConfigErrors: + """Test error handling in get_crew_config.""" + + def test_crew_not_in_manifest_raises_value_error(self, tmp_path: Path) -> None: + """Requesting a non-existent crew should raise ValueError.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " existing_crew:\n" + " description: An existing crew\n" + " agents: crews/existing/agents.yaml\n" + " tasks: crews/existing/tasks.yaml\n" + ) + with pytest.raises(ValueError, match="Crew 'nonexistent' not found"): + get_crew_config(tmp_path, "nonexistent") + + def test_error_lists_available_crews(self, tmp_path: Path) -> None: + """ValueError message should list available crew names.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " alpha_crew:\n" + " agents: a.yaml\n" + " tasks: t.yaml\n" + " beta_crew:\n" + " agents: a.yaml\n" + " tasks: t.yaml\n" + ) + with pytest.raises(ValueError, match="alpha_crew") as exc_info: + get_crew_config(tmp_path, "missing") + assert "beta_crew" in str(exc_info.value) + + def test_missing_agents_yaml_returns_empty_dict(self, tmp_path: Path) -> None: + """If agents YAML file doesn't exist, agents should be empty dict.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " test_crew:\n" + " description: Test\n" + " agents: nonexistent_agents.yaml\n" + " tasks: nonexistent_tasks.yaml\n" + ) + config = get_crew_config(tmp_path, "test_crew") + assert config["agents"] == {} + + def test_missing_tasks_yaml_returns_empty_dict(self, tmp_path: Path) -> None: + """If tasks YAML file doesn't exist, tasks should be empty dict.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " test_crew:\n" + " description: Test\n" + " agents: nonexistent_agents.yaml\n" + " tasks: nonexistent_tasks.yaml\n" + ) + config = get_crew_config(tmp_path, "test_crew") + assert config["tasks"] == {} + + def test_empty_crews_section_raises_for_any_crew(self, tmp_path: Path) -> None: + """Empty crews section should raise ValueError.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text("crews: {}\n") + with pytest.raises(ValueError, match="not found"): + get_crew_config(tmp_path, "anything") + + def test_knowledge_paths_resolves_relative(self, tmp_path: Path) -> None: + """Knowledge paths should resolve relative to config dir.""" + # Create the knowledge directory + knowledge_dir = tmp_path / "knowledge" + knowledge_dir.mkdir() + (knowledge_dir / "docs.md").write_text("# Docs") + + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text( + "crews:\n test_crew:\n agents: agents.yaml\n tasks: tasks.yaml\n knowledge:\n - knowledge\n" + ) + config = get_crew_config(tmp_path, "test_crew") + assert len(config["knowledge_paths"]) == 1 + assert config["knowledge_paths"][0] == knowledge_dir + + def test_nonexistent_knowledge_paths_excluded(self, tmp_path: Path) -> None: + """Non-existent knowledge paths should be silently excluded.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " test_crew:\n" + " agents: agents.yaml\n" + " tasks: tasks.yaml\n" + " knowledge:\n" + " - missing_dir\n" + ) + config = get_crew_config(tmp_path, "test_crew") + assert config["knowledge_paths"] == [] + + +class TestGetFrameworkFromConfigDir: + """Test edge cases for get_framework_from_config_dir.""" + + def test_unknown_directory_name_returns_none(self) -> None: + """Unknown directory name should return None.""" + result = get_framework_from_config_dir(Path("/path/.unknown")) + assert result is None + + def test_non_hidden_directory_returns_none(self) -> None: + """Non-hidden directory name should return None.""" + result = get_framework_from_config_dir(Path("/path/crewai")) + assert result is None + + def test_all_known_dirs_return_expected_framework(self) -> None: + """All known framework directories should map correctly.""" + expected = { + ".crew": None, + ".crewai": "crewai", + ".langgraph": "langgraph", + ".strands": "strands", + } + for dir_name, framework in expected.items(): + result = get_framework_from_config_dir(Path(f"/any/path/{dir_name}")) + assert result == framework, f"Expected {framework} for {dir_name}, got {result}" + + +class TestCrewConfigFrameworkConflict: + """Test framework conflict warnings in get_crew_config.""" + + def test_framework_mismatch_logs_warning(self, tmp_path: Path, caplog) -> None: + """When manifest preferred_framework differs from directory, warn.""" + crewai_dir = tmp_path / ".crewai" + crewai_dir.mkdir() + + manifest_file = crewai_dir / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " test_crew:\n" + " description: Test\n" + " agents: agents.yaml\n" + " tasks: tasks.yaml\n" + " preferred_framework: strands\n" + ) + with caplog.at_level(logging.WARNING): + config = get_crew_config(crewai_dir, "test_crew") + assert "preferred_framework=strands" in caplog.text + assert "requires crewai" in caplog.text + # required_framework should still be crewai (directory wins) + assert config["required_framework"] == "crewai" + + def test_matching_preferred_framework_no_warning(self, tmp_path: Path, caplog) -> None: + """When preferred_framework matches directory, no warning.""" + crewai_dir = tmp_path / ".crewai" + crewai_dir.mkdir() + + manifest_file = crewai_dir / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " test_crew:\n" + " description: Test\n" + " agents: agents.yaml\n" + " tasks: tasks.yaml\n" + " preferred_framework: crewai\n" + ) + with caplog.at_level(logging.WARNING): + get_crew_config(crewai_dir, "test_crew") + assert "preferred_framework" not in caplog.text + + def test_preferred_auto_no_warning(self, tmp_path: Path, caplog) -> None: + """preferred_framework='auto' should not trigger warning.""" + crewai_dir = tmp_path / ".crewai" + crewai_dir.mkdir() + + manifest_file = crewai_dir / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " test_crew:\n" + " description: Test\n" + " agents: agents.yaml\n" + " tasks: tasks.yaml\n" + " preferred_framework: auto\n" + ) + with caplog.at_level(logging.WARNING): + get_crew_config(crewai_dir, "test_crew") + assert "preferred_framework" not in caplog.text diff --git a/packages/pytest-agentic-fabric/CHANGELOG.md b/packages/pytest-agentic-fabric/CHANGELOG.md new file mode 100644 index 0000000..8b7d992 --- /dev/null +++ b/packages/pytest-agentic-fabric/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +All notable changes to this project are documented by release-please. diff --git a/packages/pytest-agentic-fabric/README.md b/packages/pytest-agentic-fabric/README.md new file mode 100644 index 0000000..609302b --- /dev/null +++ b/packages/pytest-agentic-fabric/README.md @@ -0,0 +1,16 @@ +# pytest-agentic-fabric + +Pytest fixtures for projects built on `agentic-fabric`. + +Fixtures: + +- `agentic_runtime_available`: importability predicate for optional runtimes. +- `agentic_runtime_registry`: isolated mutable registry for tests. +- `agentic_runtime_modules`: known runtime import module names. +- `agentic_mock_runtime`: installs fake runtime modules into `sys.modules`. +- `agentic_crew_config`: minimal framework-neutral crew config. +- `agentic_workspace`: temporary workspace with a discoverable `.crew` package. + +Tests marked `@pytest.mark.agentic_e2e` or +`@pytest.mark.agentic_runtime("crewai")` are skipped unless +`--agentic-e2e` is passed. diff --git a/packages/pytest-agentic-fabric/pyproject.toml b/packages/pytest-agentic-fabric/pyproject.toml new file mode 100644 index 0000000..a4651ab --- /dev/null +++ b/packages/pytest-agentic-fabric/pyproject.toml @@ -0,0 +1,56 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "pytest-agentic-fabric" +version = "0.1.0" +description = "Pytest fixtures for projects built on agentic-fabric" +requires-python = ">=3.11" +license = { text = "MIT" } +readme = "README.md" +authors = [{ name = "Jon Bogaty", email = "jon@jonbogaty.com" }] +maintainers = [{ name = "Jon Bogaty", email = "jon@jonbogaty.com" }] +classifiers = [ + "Framework :: Pytest", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Typing :: Typed", +] +dependencies = [ + "pytest>=9.0.3", +] + +[project.optional-dependencies] +tests = [ + "pytest>=9.0.3", +] +typing = [ + "mypy>=1.20.1", +] +dev = [ + "pytest-agentic-fabric[tests,typing]", + "ruff>=0.8.0", +] + +[project.entry-points.pytest11] +agentic_fabric = "pytest_agentic_fabric.plugin" + +[project.urls] +Documentation = "https://jonbogaty.com/agentic-fabric/" +Issues = "https://github.com/jbcom/agentic-fabric/issues" +Source = "https://github.com/jbcom/agentic-fabric" +Changelog = "https://github.com/jbcom/agentic-fabric/blob/main/packages/pytest-agentic-fabric/CHANGELOG.md" + +[tool.hatch.build.targets.wheel] +packages = ["src/pytest_agentic_fabric"] + +[tool.pytest.ini_options] +addopts = ["-ra", "--strict-markers", "--strict-config"] +testpaths = ["tests"] +xfail_strict = true diff --git a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/__init__.py b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/__init__.py new file mode 100644 index 0000000..fb63f50 --- /dev/null +++ b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/__init__.py @@ -0,0 +1,9 @@ +"""Pytest support for agentic-fabric.""" + +from __future__ import annotations + +from pytest_agentic_fabric.plugin import RUNTIME_MODULES + +__all__ = ["RUNTIME_MODULES", "__version__"] + +__version__ = "0.1.0" diff --git a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py new file mode 100644 index 0000000..0322b6a --- /dev/null +++ b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py @@ -0,0 +1,172 @@ +"""Pytest plugin fixtures for agentic-fabric projects.""" + +from __future__ import annotations + +from collections.abc import Callable +from importlib.util import find_spec +from pathlib import Path +from types import ModuleType +from typing import Any + +import pytest + + +RUNTIME_MODULES: dict[str, tuple[str, ...]] = { + "crewai": ( + "crewai", + "crewai.knowledge", + "crewai.knowledge.source", + "crewai.knowledge.source.text_file_knowledge_source", + ), + "langgraph": ("langgraph", "langgraph.prebuilt", "langchain_anthropic"), + "strands": ("strands",), +} + + +def pytest_addoption(parser: pytest.Parser) -> None: + """Register agentic-fabric test options.""" + parser.addoption( + "--agentic-e2e", + action="store_true", + default=False, + help="Run agentic-fabric tests that require external runtime dependencies.", + ) + + +def pytest_configure(config: pytest.Config) -> None: + """Register agentic-fabric markers.""" + config.addinivalue_line( + "markers", + "agentic_e2e: tests that require real agent runtimes, credentials, or external services", + ) + config.addinivalue_line( + "markers", + "agentic_runtime(runtime): tests that require one optional agent runtime", + ) + + +def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None: + """Skip live runtime tests unless explicitly enabled.""" + if config.getoption("--agentic-e2e"): + return + + skip_runtime = pytest.mark.skip(reason="agentic runtime test disabled; pass --agentic-e2e to run") + for item in items: + if "agentic_e2e" in item.keywords or "agentic_runtime" in item.keywords: + item.add_marker(skip_runtime) + + +@pytest.fixture +def agentic_runtime_available() -> Callable[[str], bool]: + """Return a predicate that checks whether an optional runtime can import.""" + + def check(module_name: str) -> bool: + return find_spec(module_name) is not None + + return check + + +@pytest.fixture +def agentic_runtime_registry() -> dict[str, Any]: + """Return a mutable runtime registry for tests.""" + return {} + + +@pytest.fixture +def agentic_runtime_modules() -> dict[str, tuple[str, ...]]: + """Return known import modules for optional runtimes.""" + return dict(RUNTIME_MODULES) + + +@pytest.fixture +def agentic_mock_runtime(monkeypatch: pytest.MonkeyPatch) -> Callable[[str], dict[str, ModuleType]]: + """Return a helper that installs fake runtime modules into ``sys.modules``.""" + + def install(runtime: str) -> dict[str, ModuleType]: + import sys + + modules: dict[str, ModuleType] = {} + for module_name in RUNTIME_MODULES[runtime]: + module = ModuleType(module_name) + modules[module_name] = module + monkeypatch.setitem(sys.modules, module_name, module) + parent_name, separator, child_name = module_name.rpartition(".") + if separator and parent_name in sys.modules: + setattr(sys.modules[parent_name], child_name, module) + + if "crewai" in modules: + modules["crewai"].__dict__.update({"Agent": object, "Crew": object, "Task": object}) + if "langgraph.prebuilt" in modules: + modules["langgraph.prebuilt"].__dict__["create_react_agent"] = ( + lambda *args, **kwargs: {"args": args, "kwargs": kwargs} + ) + if "strands" in modules: + modules["strands"].__dict__["Agent"] = object + return modules + + return install + + +@pytest.fixture +def agentic_crew_config() -> dict[str, Any]: + """Return a minimal framework-neutral crew config.""" + return { + "name": "test_crew", + "description": "A test crew", + "agents": { + "tester": { + "role": "Tester", + "goal": "Verify behavior", + "backstory": "Careful runtime test agent.", + } + }, + "tasks": { + "verify": { + "description": "Verify the requested behavior.", + "expected_output": "A concise result", + "agent": "tester", + } + }, + "knowledge_paths": [], + } + + +@pytest.fixture +def agentic_workspace(tmp_path: Path, agentic_crew_config: dict[str, Any]) -> Path: + """Create a minimal workspace with a ``.crew`` package.""" + workspace = tmp_path / "workspace" + crew_dir = workspace / "packages" / "sample" / ".crew" + config_dir = crew_dir / "crews" / "test_crew" + config_dir.mkdir(parents=True) + crew_dir.joinpath("manifest.yaml").write_text( + """ +name: sample +description: Test package +crews: + test_crew: + description: A test crew + agents: crews/test_crew/agents.yaml + tasks: crews/test_crew/tasks.yaml +""".strip(), + encoding="utf-8", + ) + config_dir.joinpath("agents.yaml").write_text( + """ +tester: + role: Tester + goal: Verify behavior + backstory: Careful runtime test agent. +""".strip(), + encoding="utf-8", + ) + config_dir.joinpath("tasks.yaml").write_text( + """ +verify: + description: Verify the requested behavior. + expected_output: A concise result + agent: tester +""".strip(), + encoding="utf-8", + ) + _ = agentic_crew_config + return workspace diff --git a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/py.typed b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/py.typed new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/py.typed @@ -0,0 +1 @@ + diff --git a/packages/pytest-agentic-fabric/tests/test_plugin.py b/packages/pytest-agentic-fabric/tests/test_plugin.py new file mode 100644 index 0000000..c14b18b --- /dev/null +++ b/packages/pytest-agentic-fabric/tests/test_plugin.py @@ -0,0 +1,73 @@ +"""Tests for pytest-agentic-fabric.""" + +from __future__ import annotations + +from collections.abc import Callable +from pathlib import Path +from types import ModuleType +from typing import Any + +import pytest + + +pytest_plugins = ("pytester",) + + +def test_agentic_runtime_available_fixture(agentic_runtime_available: Callable[[str], bool]) -> None: + """The runtime availability fixture should detect installed modules.""" + assert agentic_runtime_available("sys") is True + assert agentic_runtime_available("module_that_should_not_exist_for_agentic_fabric_tests") is False + + +def test_agentic_runtime_registry_fixture(agentic_runtime_registry: dict[str, Any]) -> None: + """The runtime registry fixture should be isolated and mutable.""" + agentic_runtime_registry["local"] = {"available": True} + + assert agentic_runtime_registry == {"local": {"available": True}} + + +def test_agentic_runtime_modules_fixture(agentic_runtime_modules: dict[str, tuple[str, ...]]) -> None: + """Known runtime modules should be exposed for consumer tests.""" + assert agentic_runtime_modules["crewai"][0] == "crewai" + assert "langgraph.prebuilt" in agentic_runtime_modules["langgraph"] + assert agentic_runtime_modules["strands"] == ("strands",) + + +def test_agentic_mock_runtime_fixture(agentic_mock_runtime: Callable[[str], dict[str, ModuleType]]) -> None: + """Runtime mocking fixture should install importable module objects.""" + modules = agentic_mock_runtime("langgraph") + + import langgraph.prebuilt + + assert modules["langgraph.prebuilt"] is langgraph.prebuilt + assert langgraph.prebuilt.create_react_agent("llm", [])["args"] == ("llm", []) + + +def test_agentic_crew_config_fixture(agentic_crew_config: dict[str, Any]) -> None: + """Minimal crew config should be usable by runtime tests.""" + assert agentic_crew_config["agents"]["tester"]["role"] == "Tester" + assert agentic_crew_config["tasks"]["verify"]["agent"] == "tester" + + +def test_agentic_workspace_fixture(agentic_workspace: Path) -> None: + """Workspace fixture should create a discoverable .crew package.""" + manifest = agentic_workspace / "packages" / "sample" / ".crew" / "manifest.yaml" + assert manifest.exists() + assert "test_crew" in manifest.read_text(encoding="utf-8") + + +def test_agentic_e2e_marker_skips_by_default(pytester: pytest.Pytester) -> None: + """Runtime-dependent tests should skip unless --agentic-e2e is passed.""" + pytester.makepyfile( + """ + import pytest + + @pytest.mark.agentic_e2e + def test_live_runtime(): + assert False + """ + ) + + result = pytester.runpytest("-q") + + result.assert_outcomes(skipped=1) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d15ef85 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[tool.uv] +package = false + +[tool.uv.workspace] +members = [ + "packages/agentic-fabric", + "packages/pytest-agentic-fabric", +] diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..5cb3955 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,15 @@ +{ + "release-type": "python", + "packages": { + "packages/agentic-fabric": { + "package-name": "agentic-fabric", + "component": "agentic-fabric", + "include-component-in-tag": true + }, + "packages/pytest-agentic-fabric": { + "package-name": "pytest-agentic-fabric", + "component": "pytest-agentic-fabric", + "include-component-in-tag": true + } + } +} diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..042d030 --- /dev/null +++ b/tox.ini @@ -0,0 +1,46 @@ +[tox] +env_list = lint,typecheck,py311,py312,py313,py314,plugin,docs,build +requires = + tox>=4.0 + tox-uv>=1.16 + +[testenv] +runner = uv-venv-runner +package = skip +deps = -e packages/agentic-fabric[tests] +commands = pytest packages/agentic-fabric/tests {posargs} + +[testenv:lint] +runner = uv-venv-runner +package = skip +deps = ruff>=0.8.0 +commands = ruff check packages/agentic-fabric/src packages/agentic-fabric/tests packages/agentic-fabric/examples packages/pytest-agentic-fabric/src packages/pytest-agentic-fabric/tests docs AGENTS.md README.md + +[testenv:typecheck] +runner = uv-venv-runner +package = skip +deps = + -e packages/agentic-fabric[typing] + -e packages/pytest-agentic-fabric[typing] +commands = mypy --config-file packages/agentic-fabric/pyproject.toml packages/agentic-fabric/src packages/pytest-agentic-fabric/src + +[testenv:plugin] +runner = uv-venv-runner +package = skip +deps = + -e packages/agentic-fabric + -e packages/pytest-agentic-fabric[tests] +commands = pytest packages/pytest-agentic-fabric/tests {posargs} + +[testenv:docs] +runner = uv-venv-runner +package = skip +deps = -e packages/agentic-fabric[docs] +commands_pre = python -c "import shutil; shutil.rmtree('docs/_build/html', ignore_errors=True)" +commands = sphinx-build -W -E -b html docs docs/_build/html + +[testenv:build] +runner = uv-venv-runner +package = skip +deps = uv>=0.11.19 +commands = uv build --all-packages --out-dir dist --clear diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..abae270 --- /dev/null +++ b/uv.lock @@ -0,0 +1,5490 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform != 'win32'", +] + +[manifest] +members = [ + "agentic-fabric", + "pytest-agentic-fabric", +] + +[[package]] +name = "accessible-pygments" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, +] + +[[package]] +name = "agentic-fabric" +version = "1.0.0" +source = { editable = "packages/agentic-fabric" } +dependencies = [ + { name = "pydantic" }, + { name = "pyyaml" }, +] + +[package.optional-dependencies] +ai = [ + { name = "crewai", extra = ["anthropic", "tools"] }, + { name = "langchain-anthropic" }, + { name = "langgraph" }, + { name = "strands-agents" }, +] +all = [ + { name = "beautifulsoup4" }, + { name = "crewai", extra = ["anthropic", "tools"] }, + { name = "langchain-anthropic" }, + { name = "langgraph" }, + { name = "mcp" }, + { name = "requests" }, + { name = "strands-agents" }, +] +crewai = [ + { name = "crewai", extra = ["anthropic", "tools"] }, +] +dev = [ + { name = "coverage", extra = ["toml"] }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, + { name = "pytest-timeout" }, + { name = "pytest-xdist" }, + { name = "ruff" }, + { name = "types-pyyaml" }, + { name = "types-requests" }, +] +docs = [ + { name = "furo" }, + { name = "myst-parser" }, + { name = "sphinx" }, + { name = "sphinx-autodoc2" }, + { name = "sphinx-copybutton" }, +] +langgraph = [ + { name = "langchain-anthropic" }, + { name = "langgraph" }, +] +mcp = [ + { name = "mcp" }, +] +scraping = [ + { name = "beautifulsoup4" }, + { name = "crewai", extra = ["tools"] }, + { name = "requests" }, +] +strands = [ + { name = "strands-agents" }, +] +tests = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, + { name = "pytest-timeout" }, + { name = "pytest-xdist" }, +] +typing = [ + { name = "mypy" }, + { name = "types-pyyaml" }, + { name = "types-requests" }, +] + +[package.metadata] +requires-dist = [ + { name = "agentic-fabric", extras = ["tests", "typing"], marker = "extra == 'dev'" }, + { name = "beautifulsoup4", marker = "extra == 'all'", specifier = ">=4.12.0" }, + { name = "beautifulsoup4", marker = "extra == 'scraping'", specifier = ">=4.12.0" }, + { name = "coverage", extras = ["toml"], marker = "extra == 'tests'", specifier = ">=7.6.0" }, + { name = "crewai", extras = ["tools"], marker = "extra == 'scraping'", specifier = ">=1.5.0" }, + { name = "crewai", extras = ["tools", "anthropic"], marker = "extra == 'ai'", specifier = ">=1.5.0" }, + { name = "crewai", extras = ["tools", "anthropic"], marker = "extra == 'all'", specifier = ">=1.5.0" }, + { name = "crewai", extras = ["tools", "anthropic"], marker = "extra == 'crewai'", specifier = ">=1.5.0" }, + { name = "furo", marker = "extra == 'docs'", specifier = ">=2025.12.19" }, + { name = "langchain-anthropic", marker = "extra == 'ai'", specifier = ">=0.3.0" }, + { name = "langchain-anthropic", marker = "extra == 'all'", specifier = ">=0.3.0" }, + { name = "langchain-anthropic", marker = "extra == 'langgraph'", specifier = ">=0.3.0" }, + { name = "langgraph", marker = "extra == 'ai'", specifier = ">=0.2.0" }, + { name = "langgraph", marker = "extra == 'all'", specifier = ">=0.2.0" }, + { name = "langgraph", marker = "extra == 'langgraph'", specifier = ">=0.2.0" }, + { name = "mcp", marker = "extra == 'all'", specifier = ">=1.0.0" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.0.0" }, + { name = "mypy", marker = "extra == 'typing'", specifier = ">=1.20.1" }, + { name = "myst-parser", marker = "extra == 'docs'", specifier = ">=4.0.1,<6.0.0" }, + { name = "pydantic", specifier = ">=2.13.4,<3.0.0" }, + { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.0.3" }, + { name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=0.23.0" }, + { name = "pytest-cov", marker = "extra == 'tests'", specifier = ">=7.1.0" }, + { name = "pytest-mock", marker = "extra == 'tests'", specifier = ">=3.15.1" }, + { name = "pytest-timeout", marker = "extra == 'tests'", specifier = ">=2.4.0" }, + { name = "pytest-xdist", marker = "extra == 'tests'", specifier = ">=3.6.1" }, + { name = "pyyaml", specifier = ">=6.0.3" }, + { name = "requests", marker = "extra == 'all'", specifier = ">=2.31.0" }, + { name = "requests", marker = "extra == 'scraping'", specifier = ">=2.31.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" }, + { name = "sphinx", marker = "extra == 'docs'", specifier = ">=8.2.3,<9.0.0" }, + { name = "sphinx-autodoc2", marker = "extra == 'docs'", specifier = ">=0.5.0" }, + { name = "sphinx-copybutton", marker = "extra == 'docs'", specifier = ">=0.5.2" }, + { name = "strands-agents", marker = "extra == 'ai'", specifier = ">=0.1.0" }, + { name = "strands-agents", marker = "extra == 'all'", specifier = ">=0.1.0" }, + { name = "strands-agents", marker = "extra == 'strands'", specifier = ">=0.1.0" }, + { name = "types-pyyaml", marker = "extra == 'typing'", specifier = ">=6.0.12.20240724" }, + { name = "types-requests", marker = "extra == 'typing'", specifier = ">=2.33.0.20260408" }, +] +provides-extras = ["crewai", "langgraph", "strands", "ai", "mcp", "scraping", "tests", "typing", "dev", "docs", "all"] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/78/8ea7308cac6934de8c74a14f3d5f65d1c89287426688be79538d0e5c013d/aiohttp-3.14.1.tar.gz", hash = "sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035", size = 7955794, upload-time = "2026-06-07T21:09:35.529Z" } +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/8f/e1/a2872aa55495a70f61310d411541c6ee23812d9a884e000c716e1bc3edbf/aiohttp-3.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c1af67559445498b502030c35c59db59966f47041ca9de5b4e707f86bd10b5f", size = 518743, upload-time = "2026-06-07T21:06:09.749Z" }, + { 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/b5/63/a251a9d2a6cb45065b2ddc0bde2b3dd10108740a9a42f632c66405a761a2/aiohttp-3.14.1-cp311-cp311-win32.whl", hash = "sha256:313701e488100074ce99850404ee36e741abf6330179fec908a1944ecf570126", size = 458386, upload-time = "2026-06-07T21:06:35.279Z" }, + { url = "https://files.pythonhosted.org/packages/17/ca/69274c51dcd6e8947d77b2806cf47a4a15f2c846e2cbeb1882547d3da283/aiohttp-3.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:03ab4530fdcb3a543a122ba4b65ac9919da9fe9f78a03d328a6e38ff962f7aa5", size = 483406, upload-time = "2026-06-07T21:06:36.824Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8a/c25904f77690c3688ec140f87591ef11a0cfe36bf3d5c0f1f38056fb62b3/aiohttp-3.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:486f7d16ed54c39c2cbd7ca71fd8ba2b8bb7860df65bd7b6ed640bab96a38a8b", size = 452987, upload-time = "2026-06-07T21:06:38.371Z" }, + { 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/c2/82/280619e0bd7bf2454987e19282616e84762255dd9c8468f62382e8c191f1/aiohttp-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d", size = 512310, upload-time = "2026-06-07T21:06:42.207Z" }, + { 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/3e/61/a0c0a8f327a9c52095cdd8e312391b00d3ed64ab6c72bb5c33d8ec251cf7/aiohttp-3.14.1-cp312-cp312-win32.whl", hash = "sha256:ec8dc383ee57ea3e883477dcca3f11b65d58199f1080acaf4cd6ad9a99698be4", size = 452771, upload-time = "2026-06-07T21:07:09.669Z" }, + { url = "https://files.pythonhosted.org/packages/df/d9/ea367c75f16ac9c6cdc8febb25e8318fa21a2b1bc8d6514d4b2d890bface/aiohttp-3.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2aa92c87868cd13674989f9ee83e5f9f7ea4237589b728048e1f0c8f6caa3271", size = 479873, upload-time = "2026-06-07T21:07:11.538Z" }, + { url = "https://files.pythonhosted.org/packages/03/64/8d96784a7851156db8a4c6c3f6f91042fdf39fb15a4cc38c8b3c14833c45/aiohttp-3.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:2c840c90759922cb5e6dda94596e079a30fb5a5ba548e7e0dc00574703940847", size = 448073, upload-time = "2026-06-07T21:07:13.637Z" }, + { url = "https://files.pythonhosted.org/packages/bc/97/bd137012dd97e1649162b099135a80e1fd59aaa807b2430fc448d1029aff/aiohttp-3.14.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:b3a03285a7f9c7b016324574a6d92a1c895da6b978cb8f1deee3ac72bc6da178", size = 506882, upload-time = "2026-06-07T21:07:15.501Z" }, + { url = "https://files.pythonhosted.org/packages/ef/79/e5cc690e9d922a66887ceeaca53a8ffd5a7b0be3816142b7abc433742d89/aiohttp-3.14.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:2a73f487ab8ef5abbb24b7aa9b73e98eaba9e9e031804ff2416f02eca315ccaf", size = 515270, upload-time = "2026-06-07T21:07:17.53Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/78/c0/5ebe5270a7c140d7c6f79dcb018640225f14d406c149e4eec04a7d82fe71/aiohttp-3.14.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1b9748363260121d2927704f5d4fc498150669ca3ae93625986ee89c8f80dcd4", size = 501564, upload-time = "2026-06-07T21:07:23.388Z" }, + { 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/b2/f4/c4227aacfacc5cb0cc2d119b65301d177912a6842cd64e120c47af76064f/aiohttp-3.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4dfd6e47d3c44c2279907607f73a4240b88c69eb8b90da7e2441a8045dfd21da", size = 510918, upload-time = "2026-06-07T21:07:27.28Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/e4/ae/dbce10533d3896d544d5053939ed75b7dc31a1b0973d959b1b5ae21028d6/aiohttp-3.14.1-cp313-cp313-win32.whl", hash = "sha256:e509a55f681e6158c20f70f102f9cf61fb20fbc382272bc6d94b7343f2582780", size = 452662, upload-time = "2026-06-07T21:07:56.06Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/0bf1a19362c32f06229da5e7ddfcec91f93474d6307f7a2d3135e9c674dc/aiohttp-3.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:1ac8531b638959718e18c2207fbfe297819875da46a740b29dfa29beba64355a", size = 479748, upload-time = "2026-06-07T21:07:58.319Z" }, + { url = "https://files.pythonhosted.org/packages/22/0a/62e7232dc9484fbec112ceb32efb6a624cc7994ec6e2b019286f17c4e8f2/aiohttp-3.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:250d14af67f6b6a1a4a811049b1afa69d61d617fca6bf33149b3ab1a6dbcf7b8", size = 447723, upload-time = "2026-06-07T21:08:00.154Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a1/5fafa04e1ca91ddb47608699d60649c1c6db3cf41c99e78fc4056f9513db/aiohttp-3.14.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15", size = 508531, upload-time = "2026-06-07T21:08:02.093Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/bfa02f699d87ffc86d5959270b28f1cb410add3ccaced8ed2e0b8a5238fc/aiohttp-3.14.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8", size = 514718, upload-time = "2026-06-07T21:08:04.476Z" }, + { url = "https://files.pythonhosted.org/packages/85/a5/9594ad6289eebbc97d167c44213d557807f90e59115caad24de21ad2c3b1/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3", size = 487918, upload-time = "2026-06-07T21:08:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/b4/61/16a32c36c3c49edec122a3dc811f2057df2f94d3b14aa107c8017d981618/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba", size = 494014, upload-time = "2026-06-07T21:08:08.263Z" }, + { url = "https://files.pythonhosted.org/packages/9b/89/3ebcf96ed99c05bec9c434aaac6963fd3cbab4a786ae739908a144d9ce44/aiohttp-3.14.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397", size = 502398, upload-time = "2026-06-07T21:08:10.244Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3d/b74870a0c2d40c355928cd5b96c7a11fa821b8a40fc41365e64479b151fb/aiohttp-3.14.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448", size = 758018, upload-time = "2026-06-07T21:08:12.447Z" }, + { url = "https://files.pythonhosted.org/packages/d3/66/f42f5c984d99e49c6cff5f26f590750f2e2f7ef1fcfb99966ab5be1b632e/aiohttp-3.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004", size = 512462, upload-time = "2026-06-07T21:08:14.624Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a7/248e1aebe0c7810b0271e021a0f2a5eb6e78a051885b3c9df49f42a5802d/aiohttp-3.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983", size = 512824, upload-time = "2026-06-07T21:08:16.572Z" }, + { url = "https://files.pythonhosted.org/packages/26/97/2aa0e5ba0727dc3bd5aaebb7ccbc510f7dfb7fb961ec87497cd496635ab1/aiohttp-3.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe", size = 1749898, upload-time = "2026-06-07T21:08:18.635Z" }, + { url = "https://files.pythonhosted.org/packages/00/8d/e97f6c96c891d457c8479d92a514ba194d0412f981d72c70341ee18488ed/aiohttp-3.14.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333", size = 1710114, upload-time = "2026-06-07T21:08:20.892Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e6/aa8d7e863048c8fceb5cd6ce74017311cec3ead07847387e12265fb4444e/aiohttp-3.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0", size = 1802541, upload-time = "2026-06-07T21:08:23.044Z" }, + { url = "https://files.pythonhosted.org/packages/83/a8/72193137de57fda4ebfae4563182d082c8856e3b6e9871d0b46f028fb369/aiohttp-3.14.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a", size = 1875776, upload-time = "2026-06-07T21:08:25.288Z" }, + { url = "https://files.pythonhosted.org/packages/a0/18/938441025db6769a3464596b2410af3afde0b21eb2f204c6f766f68af4bd/aiohttp-3.14.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602", size = 1760329, upload-time = "2026-06-07T21:08:27.363Z" }, + { url = "https://files.pythonhosted.org/packages/60/29/bf2496b4065e76e09fe48015aaffe5ce161d8f089b06ac6982070f653076/aiohttp-3.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca", size = 1587293, upload-time = "2026-06-07T21:08:29.805Z" }, + { url = "https://files.pythonhosted.org/packages/49/a2/2136674d52123b1354bd05dd5753c318db47dc0c927cc70b27bab3755456/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35", size = 1714756, upload-time = "2026-06-07T21:08:32.094Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b9/e5fd2e6f915503081c0f9b1e8540947037929c70c191da2e4d54b31a21a1/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844", size = 1721052, upload-time = "2026-06-07T21:08:34.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/5a/2833e324a2263e104e31e2e91bc5bbee81bc499afd32203faee048a883f0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496", size = 1766888, upload-time = "2026-06-07T21:08:36.95Z" }, + { url = "https://files.pythonhosted.org/packages/57/fa/dea6511870913162f3b2e8c42a7614eb203a4540b8c2da43e0bfb0548f3c/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5", size = 1581679, upload-time = "2026-06-07T21:08:39.292Z" }, + { url = "https://files.pythonhosted.org/packages/14/bd/3cf0d55e71784b33534e9710a67d382d900598b4787fbce6cc7317f8c42a/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95", size = 1782021, upload-time = "2026-06-07T21:08:41.407Z" }, + { url = "https://files.pythonhosted.org/packages/c1/af/14bb5843eccbe234f4dfb78ab73e549d99727247e62ae5d62cbd22eaf5b0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444", size = 1742574, upload-time = "2026-06-07T21:08:43.795Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1e/fbeb7af9210a67ac0f9c9bec0f8f4568497924e33137a3d5b48e1cf85f3f/aiohttp-3.14.1-cp314-cp314-win32.whl", hash = "sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0", size = 457773, upload-time = "2026-06-07T21:08:46.168Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2b/13e8d741a9ec5db7d900c060554cf8352ab85e44e2a4469ebb9d377bda17/aiohttp-3.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719", size = 485001, upload-time = "2026-06-07T21:08:48.401Z" }, + { url = "https://files.pythonhosted.org/packages/df/30/491acfa2c4d6c3ff59c49a14fc1b50be3241e25bbb0c84c09e2da4d11395/aiohttp-3.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec", size = 453809, upload-time = "2026-06-07T21:08:50.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/e3/19dbe1a1f4cc6230eb9e314de7fe68053b0992f9302b27d12141a0b5db53/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2", size = 793320, upload-time = "2026-06-07T21:08:52.775Z" }, + { url = "https://files.pythonhosted.org/packages/7f/20/1b7182219ba1b108430d6e4dc53d25ae02dcfcf5a045b33af4e8c5167527/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340", size = 529077, upload-time = "2026-06-07T21:08:55Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c8/14ce60ec31a2e5f5274bb17d383a6f7a3aabca31ac04eee05585bbadab16/aiohttp-3.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d", size = 532476, upload-time = "2026-06-07T21:08:57.176Z" }, + { url = "https://files.pythonhosted.org/packages/7e/02/9ac85e081e53da2e061b02fa7758fe0a12d17b8ce2d1f5e6c7cb76730328/aiohttp-3.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94", size = 1922347, upload-time = "2026-06-07T21:08:59.563Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3e/d3ba07a0ab38b5389e10bec4362d21e10a4f667cba2d79ba30837b3a5059/aiohttp-3.14.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc", size = 1786465, upload-time = "2026-06-07T21:09:01.909Z" }, + { url = "https://files.pythonhosted.org/packages/0b/cb/e2ee978a00cfb2df829704a69528b18154eba5939f45bc1efa8f33aee4c5/aiohttp-3.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251", size = 1909423, upload-time = "2026-06-07T21:09:04.357Z" }, + { url = "https://files.pythonhosted.org/packages/73/5d/1430334858b1022b58ae50399a918f0bd6fe8fa7fa183598d657ff61e040/aiohttp-3.14.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1", size = 2001906, upload-time = "2026-06-07T21:09:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/66/4e/560c7472d3d198a23aa5c8b19a5115bf6a9b77b7d3e4bb363da320430ad2/aiohttp-3.14.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3", size = 1877095, upload-time = "2026-06-07T21:09:09.011Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f1/4745806578d447db4a784a8591e2dae3afdfc2bcb96f8f81271b13df6543/aiohttp-3.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c", size = 1676222, upload-time = "2026-06-07T21:09:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c9/48255813cca749a229ef0ab476004ec623728ad79a9c0840616f6c076325/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203", size = 1842922, upload-time = "2026-06-07T21:09:14.118Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c0/bbd054e2bee909f529523a5af3891052606af5143c09f5f183ec3b234676/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1", size = 1825035, upload-time = "2026-06-07T21:09:16.447Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ae/90395d4376deceb74e09ec26b6adf7d2015a6f8802d6d84446af860fef04/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665", size = 1849512, upload-time = "2026-06-07T21:09:18.742Z" }, + { url = "https://files.pythonhosted.org/packages/93/bd/fb25f3049957553d4ce0ba6ae480aa2f592a6985497fca590837d16c1be0/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1", size = 1668571, upload-time = "2026-06-07T21:09:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/3f/22/7f73303d64dd567ff3addca90b556690ed1233a47b8f55d242fb90af3681/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d", size = 1881159, upload-time = "2026-06-07T21:09:23.813Z" }, + { url = "https://files.pythonhosted.org/packages/44/be/0474c5a8b5640e1e4aa1923430a91f4151be82e511373fe764189b89aef5/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c", size = 1841409, upload-time = "2026-06-07T21:09:26.207Z" }, + { url = "https://files.pythonhosted.org/packages/7b/3c/bb4a7cba26956cb3da4553cc2056cf67be5b5ff6e6d8fa4fbdff73bfb7ae/aiohttp-3.14.1-cp314-cp314t-win32.whl", hash = "sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365", size = 494166, upload-time = "2026-06-07T21:09:28.505Z" }, + { url = "https://files.pythonhosted.org/packages/8a/84/ec80c2c1f66a952555a9f86df6b33af65108a6febfa0471b69013a12f807/aiohttp-3.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9", size = 530255, upload-time = "2026-06-07T21:09:30.843Z" }, + { url = "https://files.pythonhosted.org/packages/2a/71/6e22be134a4061ada85a92951b842f2657f17d926b727f3f94c56ae963d6/aiohttp-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6", size = 469640, upload-time = "2026-06-07T21:09:33.028Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anthropic" +version = "0.112.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "docstring-parser" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/dd/808c144d4a883fcfd12fe0d7689b1d86bbbea6666c1cc957ad19f1017c22/anthropic-0.112.0.tar.gz", hash = "sha256:e180cd91aa5b9b32e4007fe69892ab128d8a86b9f90825103b1903fbc977d0af", size = 937460, upload-time = "2026-06-24T18:45:56.844Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/26/ea71185027956325be1903d4fcaf7461d5ef40ca8f0e64f992e24ea9db0e/anthropic-0.112.0-py3-none-any.whl", hash = "sha256:bcc6268612c716dbb77133dd60fc41d26016d1b81dee9a52314d210193638751", size = 931954, upload-time = "2026-06-24T18:45:58.205Z" }, +] + +[[package]] +name = "anyio" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, +] + +[[package]] +name = "appdirs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, +] + +[[package]] +name = "ast-serialize" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" }, + { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" }, + { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" }, + { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" }, + { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" }, + { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" }, + { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" }, + { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" }, + { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" }, + { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" }, + { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" }, + { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" }, + { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" }, + { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, +] + +[[package]] +name = "astroid" +version = "3.3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/74/dfb75f9ccd592bbedb175d4a32fc643cf569d7c218508bfbd6ea7ef9c091/astroid-3.3.11.tar.gz", hash = "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce", size = 400439, upload-time = "2025-07-13T18:04:23.177Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/0f/3b8fdc946b4d9cc8cc1e8af42c4e409468c84441b933d037e101b3d72d86/astroid-3.3.11-py3-none-any.whl", hash = "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec", size = 275612, upload-time = "2025-07-13T18:04:21.07Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + +[[package]] +name = "bcrypt" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806, upload-time = "2025-09-25T19:49:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626, upload-time = "2025-09-25T19:49:06.723Z" }, + { url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853, upload-time = "2025-09-25T19:49:08.028Z" }, + { url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793, upload-time = "2025-09-25T19:49:09.727Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930, upload-time = "2025-09-25T19:49:11.204Z" }, + { url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194, upload-time = "2025-09-25T19:49:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381, upload-time = "2025-09-25T19:49:14.308Z" }, + { url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750, upload-time = "2025-09-25T19:49:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757, upload-time = "2025-09-25T19:49:17.244Z" }, + { url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740, upload-time = "2025-09-25T19:49:18.693Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197, upload-time = "2025-09-25T19:49:20.523Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974, upload-time = "2025-09-25T19:49:22.254Z" }, + { url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498, upload-time = "2025-09-25T19:49:24.134Z" }, + { url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853, upload-time = "2025-09-25T19:49:25.702Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626, upload-time = "2025-09-25T19:49:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862, upload-time = "2025-09-25T19:49:28.365Z" }, + { url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544, upload-time = "2025-09-25T19:49:30.39Z" }, + { url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787, upload-time = "2025-09-25T19:49:32.144Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753, upload-time = "2025-09-25T19:49:33.885Z" }, + { url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587, upload-time = "2025-09-25T19:49:35.144Z" }, + { url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178, upload-time = "2025-09-25T19:49:36.793Z" }, + { url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295, upload-time = "2025-09-25T19:49:38.164Z" }, + { url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700, upload-time = "2025-09-25T19:49:39.917Z" }, + { url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034, upload-time = "2025-09-25T19:49:41.227Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766, upload-time = "2025-09-25T19:49:43.08Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449, upload-time = "2025-09-25T19:49:44.971Z" }, + { url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310, upload-time = "2025-09-25T19:49:46.162Z" }, + { url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761, upload-time = "2025-09-25T19:49:47.345Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" }, + { url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" }, + { url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" }, + { url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" }, + { url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" }, + { url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" }, + { url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" }, + { url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" }, + { url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" }, + { url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" }, + { url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" }, + { url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" }, + { url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" }, + { url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" }, + { url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" }, + { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" }, + { url = "https://files.pythonhosted.org/packages/8a/75/4aa9f5a4d40d762892066ba1046000b329c7cd58e888a6db878019b282dc/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7edda91d5ab52b15636d9c30da87d2cc84f426c72b9dba7a9b4fe142ba11f534", size = 271180, upload-time = "2025-09-25T19:50:38.575Z" }, + { url = "https://files.pythonhosted.org/packages/54/79/875f9558179573d40a9cc743038ac2bf67dfb79cecb1e8b5d70e88c94c3d/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:046ad6db88edb3c5ece4369af997938fb1c19d6a699b9c1b27b0db432faae4c4", size = 273791, upload-time = "2025-09-25T19:50:39.913Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fe/975adb8c216174bf70fc17535f75e85ac06ed5252ea077be10d9cff5ce24/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dcd58e2b3a908b5ecc9b9df2f0085592506ac2d5110786018ee5e160f28e0911", size = 270746, upload-time = "2025-09-25T19:50:43.306Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f8/972c96f5a2b6c4b3deca57009d93e946bbdbe2241dca9806d502f29dd3ee/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:6b8f520b61e8781efee73cba14e3e8c9556ccfb375623f4f97429544734545b4", size = 273375, upload-time = "2025-09-25T19:50:45.43Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" }, +] + +[[package]] +name = "boto3" +version = "1.43.36" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/9f/897287e955db0f50b12fd69ef45956e4fd2c7ddb48c736872f7ea2314443/boto3-1.43.36.tar.gz", hash = "sha256:587d7ee92a12e440ad12b0e7f11f3358f0c4d65b19f64726efc94aaf194aff28", size = 112690, upload-time = "2026-06-23T02:47:14.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/f1/274303f52483ecf199eae6f8d9b6f5951670397ee4d72c06cfd4eb644612/boto3-1.43.36-py3-none-any.whl", hash = "sha256:42942dde254673abcbc9e6e60017c88341a4f49d99d24e1f2e290fb38138c26f", size = 140031, upload-time = "2026-06-23T02:47:13.178Z" }, +] + +[[package]] +name = "botocore" +version = "1.43.36" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/37/da9e7f6ca73ac73afd7f0bb7f238aa5daba35c081e98d7f48a7c399599c0/botocore-1.43.36.tar.gz", hash = "sha256:4cae47d1b2d426316b85a0087d9e69e048f13bc003b5177d74639fe9dfd28205", size = 15625488, upload-time = "2026-06-23T02:47:03.192Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/19/934f81592527a3f7f9b943c893e334c721a4644948642bc33885d584e9ec/botocore-1.43.36-py3-none-any.whl", hash = "sha256:3c65fdc39ed01d8dfde1e961b34038aed03c459f8ddf80717a12ac006475e49d", size = 15313630, upload-time = "2026-06-23T02:46:59.327Z" }, +] + +[[package]] +name = "build" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" }, +] + +[[package]] +name = "certifi" +version = "2026.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { 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/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { 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/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { 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/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "chromadb" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bcrypt" }, + { name = "build" }, + { name = "grpcio" }, + { name = "httpx" }, + { name = "importlib-resources" }, + { name = "jsonschema" }, + { name = "kubernetes" }, + { name = "mmh3" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "onnxruntime" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-grpc" }, + { name = "opentelemetry-sdk" }, + { name = "orjson" }, + { name = "overrides" }, + { name = "posthog" }, + { name = "pybase64" }, + { name = "pydantic" }, + { name = "pypika" }, + { name = "pyyaml" }, + { name = "rich" }, + { name = "tenacity" }, + { name = "tokenizers" }, + { name = "tqdm" }, + { name = "typer" }, + { name = "typing-extensions" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/48/11851dddeadad6abe36ee071fedc99b5bdd2c324df3afa8cb952ae02798b/chromadb-1.1.1.tar.gz", hash = "sha256:ebfce0122753e306a76f1e291d4ddaebe5f01b5979b97ae0bc80b1d4024ff223", size = 1338109, upload-time = "2025-10-05T02:49:14.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/59/0d881a9b7eb63d8d2446cf67fcbb53fb8ae34991759d2b6024a067e90a9a/chromadb-1.1.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:27fe0e25ef0f83fb09c30355ab084fe6f246808a7ea29e8c19e85cf45785b90d", size = 19175479, upload-time = "2025-10-05T02:49:12.525Z" }, + { url = "https://files.pythonhosted.org/packages/94/4f/5a9fa317c84c98e70af48f74b00aa25589626c03a0428b4381b2095f3d73/chromadb-1.1.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:95aed58869683f12e7dcbf68b039fe5f576dbe9d1b86b8f4d014c9d077ccafd2", size = 18267188, upload-time = "2025-10-05T02:49:09.236Z" }, + { url = "https://files.pythonhosted.org/packages/45/1a/02defe2f1c8d1daedb084bbe85f5b6083510a3ba192ed57797a3649a4310/chromadb-1.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06776dad41389a00e7d63d936c3a15c179d502becaf99f75745ee11b062c9b6a", size = 18855754, upload-time = "2025-10-05T02:49:03.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0d/80be82717e5dc19839af24558494811b6f2af2b261a8f21c51b872193b09/chromadb-1.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bba0096a7f5e975875ead23a91c0d41d977fbd3767f60d3305a011b0ace7afd3", size = 19893681, upload-time = "2025-10-05T02:49:06.481Z" }, + { url = "https://files.pythonhosted.org/packages/2d/6e/956e62975305a4e31daf6114a73b3b0683a8f36f8d70b20aabd466770edb/chromadb-1.1.1-cp39-abi3-win_amd64.whl", hash = "sha256:a77aa026a73a18181fd89bbbdb86191c9a82fd42aa0b549ff18d8cae56394c8b", size = 19844042, upload-time = "2025-10-05T02:49:16.925Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/91/0a7c28934e50d8ac9a7b117712d176f2953c3170bccced5eaacfa3e96175/coverage-7.14.3.tar.gz", hash = "sha256:1a7563a443f3d53fdeb040ec8c9f7466aed7ca3dc5891aa09d3ca3625fa4387f", size = 924398, upload-time = "2026-06-22T23:10:25.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/24/efb17eb94018dd3415d0e8a76a4786a866e8964aa9c50f033399d23939c2/coverage-7.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e574801e1d643561594aa021206c46d80b257e9853087090ba97bed8b0a509d3", size = 220501, upload-time = "2026-06-22T23:08:02.182Z" }, + { url = "https://files.pythonhosted.org/packages/76/93/32f1bfca6cdd34259c8af42820a034b7a28dfb44969a13ed38c17e0ba5b0/coverage-7.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f82b6bb7d75a2613e85d07cefa3a8c973d0544a8993337f6e2728e4a1e94c305", size = 221008, upload-time = "2026-06-22T23:08:03.701Z" }, + { url = "https://files.pythonhosted.org/packages/eb/88/0d0f974855ff905d15a64f7873d00bdc4182e2736267486c6634f4af293c/coverage-7.14.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a2335ea5fed26af2e831094964fa3f8fae60b45f7e37fcc2d3b615b2add3ad87", size = 251420, upload-time = "2026-06-22T23:08:05.211Z" }, + { url = "https://files.pythonhosted.org/packages/39/7f/117dd2ec65e4140576f8ef991d88220f9b806769f7a8c20e0550c0f924e2/coverage-7.14.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fbb8c3a98e779013786ae01d229662aeacbc77100efbd3f2f245219ace5af700", size = 253331, upload-time = "2026-06-22T23:08:06.672Z" }, + { url = "https://files.pythonhosted.org/packages/87/55/f0bd6d6538e3f16829fb8a44b6c0d2fe9da638bbfdd6a20f8b5da8f4fa81/coverage-7.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac082660de8f429ba0ea363595abb838998570b9a7546777c60f413ab902bbde", size = 255441, upload-time = "2026-06-22T23:08:08.208Z" }, + { url = "https://files.pythonhosted.org/packages/1e/98/aa71f7879019c846a8a9662579ea4484b0202cf1e252ffeed647075e7eca/coverage-7.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac012839ff7e396030f1e94e10553a431d14e4de2ab65cb3acb72bbd5628ca2", size = 257398, upload-time = "2026-06-22T23:08:09.749Z" }, + { url = "https://files.pythonhosted.org/packages/f3/4f/5fd367e59844190f5965015d7bee899e67a89d13eb2760118479bf836f2f/coverage-7.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5952f8c1bda2a5347154450379316e6dfa4d934d62ca35f6784451e6f55074fb", size = 251558, upload-time = "2026-06-22T23:08:11.37Z" }, + { url = "https://files.pythonhosted.org/packages/8f/de/5383a6ee5a6376701fe07d980fa8e4a66c0c377fead16712720340d701a3/coverage-7.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8cf0f2509acb4619e2471a1951089054dd58ebea7a912066d2ea56dd4c24ca4a", size = 253134, upload-time = "2026-06-22T23:08:13.04Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/09542b1a99f788e3daec7f0fadc288821e71aca9ea298d51bfa1ba79fed5/coverage-7.14.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2e41fd3aab806770008279a93879b0924b16247e09ab537c043d08bbca53b4ab", size = 251195, upload-time = "2026-06-22T23:08:14.606Z" }, + { url = "https://files.pythonhosted.org/packages/02/9d/722fe8c13f0fbb064491b9e8656e56a606286792e5068c47ca1042e773e8/coverage-7.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f0a47095963cfe054e0df178daca95aec21e680d6076da807c3add28dfe920f7", size = 254959, upload-time = "2026-06-22T23:08:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/fb/58/943627179ff1d82da9e54d0a5b0bb907bb19cf19515599ccd921de50b469/coverage-7.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a090cbf9521e78ffdb2fcf448b72902afe9f5923ff6a12d5c0d0120200348af9", size = 250914, upload-time = "2026-06-22T23:08:18.03Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d4/803efcbf9ae5567454a0c71e983589529448e2704ee0da2dc0163d482f18/coverage-7.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d310baf69a4fbe8a098ce727e4808a34866ac718a6f759ae659cbd3221358bc", size = 251824, upload-time = "2026-06-22T23:08:19.704Z" }, + { url = "https://files.pythonhosted.org/packages/32/79/3f78ea9563132746eed5cecb75d2e576f9d8fec45a47242b5ae0950b82a3/coverage-7.14.3-cp311-cp311-win32.whl", hash = "sha256:74fdd718d88fe144f4579b8747873a07ec3f04cb837d5faec5a25d9e22fa31a8", size = 222594, upload-time = "2026-06-22T23:08:21.311Z" }, + { url = "https://files.pythonhosted.org/packages/85/22/9ebbc5a2ab42ac5d0eea1f48648629e1de9bbe41ec243ed6b93d55a5a53f/coverage-7.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:cc96aa922e21d4bc5d5ed3c915cef27dfcbc13686f47d5e378d647fbfba655a2", size = 223073, upload-time = "2026-06-22T23:08:23.318Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/69d5fcc16cb555153f99cec5467922f226be0369f7335a9506856d2a7bd0/coverage-7.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:c66f9f9d4f1e9712eb9b1de5310f881d4e2188cfcba5065e1a8490f38687f2c4", size = 222617, upload-time = "2026-06-22T23:08:25.054Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b0/8a911f6ffe6974dac4df95b468ab9a2899d0e59f0f99a489afeec39f00bc/coverage-7.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d74ff26299c4879ce3a4d826f9d3d4d556fd285fde7bbce3c0ef5a8ab1cec24", size = 220672, upload-time = "2026-06-22T23:08:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/0fc0cb52538783dbbae0934b834f5a58fd5354380ee6cad4a07b15dc845d/coverage-7.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:96150a9cf3468ea20f0bc5d0e21b3df8972c31480ef90fa7614b773cc6429665", size = 221035, upload-time = "2026-06-22T23:08:28.372Z" }, + { url = "https://files.pythonhosted.org/packages/77/e2/421ccfbb48335ac49e93301478cf5d623b0c2bf1c0cadd8e2b2fc6c0c710/coverage-7.14.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:27d07a46500ba23515b838dbcf52512026af04090755cf6cc64166d88c9b9a1a", size = 252540, upload-time = "2026-06-22T23:08:30.226Z" }, + { url = "https://files.pythonhosted.org/packages/06/c2/05b8c890097c61a7f4406b35396b997a635200ded0339eda83dfbe526c5f/coverage-7.14.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:621e13c6108234d7960aaf5762ab5c3c00f33c30c15af06dcbff0c73bf112727", size = 255274, upload-time = "2026-06-22T23:08:31.876Z" }, + { url = "https://files.pythonhosted.org/packages/dc/be/b6d9efe447f8ba3c3c854195f326bd64c54b907d936cd2fdebf8767ec72e/coverage-7.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b60ca6d8af70473491a15a343cbabab2e8f9ea66a4376e81c7aa24876a6f977", size = 256389, upload-time = "2026-06-22T23:08:33.843Z" }, + { url = "https://files.pythonhosted.org/packages/d4/3c/f26e50acc429e608bc534ac06f0a3c169019c798178ec5e9de3dbc0df9c9/coverage-7.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c90a7cdd5e380e1ce02f19792e2ac2fbfbf177e35a27e69fd3e873b30d895c0c", size = 258648, upload-time = "2026-06-22T23:08:35.481Z" }, + { url = "https://files.pythonhosted.org/packages/9e/a2/01c1fabf816c8e1dae197e258edf878a3d3ddc86fbda34b76e5794277d8f/coverage-7.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d788e5fd55347eef06ca0732c77d04a264de67e8ff24631270cdff3767a60cf", size = 252949, upload-time = "2026-06-22T23:08:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/89/c6/941166dd79c31fd44a13063780ae8d552eee0089a0a0930b9bdb7df554ed/coverage-7.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62c7f79db2851c95ef020e5d28b97afde3daf9f7febcd35b53e05638f729063f", size = 254310, upload-time = "2026-06-22T23:08:39.174Z" }, + { url = "https://files.pythonhosted.org/packages/10/31/80b1fd028201a961033ce95be3cd1e39e521b3762e6b4a1ac1616cb291e7/coverage-7.14.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:90f7608aeb5d9b60b523b9fb2a4ee1973867cc4865a3f26fe6c7577073b70205", size = 252453, upload-time = "2026-06-22T23:08:40.84Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/c3d9addd94c4b524f3f4af0232075f5fe7170ce99a1386edff803e5934db/coverage-7.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1e3b91f9c4740aeb571ecf82e5e8d8e4ab62d34fcb5a5d4e5baa38c6f7d2857c", size = 256522, upload-time = "2026-06-22T23:08:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/91/14/e5a0575f73795af3a7a9ae13dadf812e17d32422896839987dc3f86947e1/coverage-7.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c946099774a7699de03cbd0ff0a64e21aed4525eed9d959adde4afe6d15758ef", size = 252023, upload-time = "2026-06-22T23:08:44.243Z" }, + { url = "https://files.pythonhosted.org/packages/38/9b/9652ee531937ce3b8a63a8896885b2b4a2d56adc30e53c9540c666286d88/coverage-7.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16b206e521feb8b7133a45754643dead0538489cf8b783b90cf5f4e3299625fd", size = 253893, upload-time = "2026-06-22T23:08:46.113Z" }, + { url = "https://files.pythonhosted.org/packages/b1/05/42678841c8c38e4b08bdfc48269f5a16dfbf5806000fe6a89b4cece3c691/coverage-7.14.3-cp312-cp312-win32.whl", hash = "sha256:ea3169c7116eb6cdf7608c6c7da9ecfcb3da40688e3a510fac2d1d2bafd6dc35", size = 222734, upload-time = "2026-06-22T23:08:47.858Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/07a4fcee55177a25f1b52331a8e92cf4f2c53b1a9c75ce2981fd59c684ad/coverage-7.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:7ea52fc08f007bcc494d4bb3df3851e95843d881860ba38fe2c64dc100db5e7d", size = 223266, upload-time = "2026-06-22T23:08:49.494Z" }, + { url = "https://files.pythonhosted.org/packages/aa/34/2b8b66a989282ea7b370beb49f50bab29470dc30bb0b03935b6b802782f7/coverage-7.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:8cec0ad652ec57790970d817490105bd917d783c2f7b38d6b58a0ca312e1a336", size = 222655, upload-time = "2026-06-22T23:08:51.766Z" }, + { url = "https://files.pythonhosted.org/packages/a9/83/7fefbf5df23ed2b7f489907564a7b34b9b07098128e12e0fdfa92626e456/coverage-7.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47968988b367990ae4ab17523790c38cd125e02c6bfd379b6022be2d40bdc38c", size = 220699, upload-time = "2026-06-22T23:08:53.522Z" }, + { url = "https://files.pythonhosted.org/packages/31/e6/38c3653ff6d56d704b29241362387ca824e38e15b76fdcb7096538195790/coverage-7.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0ee68f5c34812780f3a7063382c0a9fcbb99985b7ddcdcaa626e4f3fb2e0783a", size = 221068, upload-time = "2026-06-22T23:08:55.571Z" }, + { url = "https://files.pythonhosted.org/packages/20/86/4f5c45d51c5cd10a128933f0fd235393c9146abbfd2ce2dfa68b3267ead3/coverage-7.14.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fa9e5c6857a7e80fa22ace5cf3550ae392bbfc322f1d8dd2d2d5a8be38cec027", size = 252060, upload-time = "2026-06-22T23:08:57.464Z" }, + { url = "https://files.pythonhosted.org/packages/82/50/dfce42eff2cecabcd5a9bbad5489449c87db3415f408d23ffee417ce01f6/coverage-7.14.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98a0859b0e98e43e1178a9402e19c8127766b14f7109a374d976e5a62c0e5c73", size = 254657, upload-time = "2026-06-22T23:08:59.453Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d2/639ceb1bc8038fd0d66768278d5dc22df3391918b8278c2a21aa2602a531/coverage-7.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69918344541ed9c8368566c2adc03c0e33d4550d7faa87d1b35e49b6a3286ea9", size = 255892, upload-time = "2026-06-22T23:09:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/8b/96/002094a10e113512500dc1e10430a449417e17b0f90f7d496bcb820208b7/coverage-7.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b7f300ac92cd4b570724c8ffbbd0c130fee298d2447f41d5a3abf58976fae1de", size = 258026, upload-time = "2026-06-22T23:09:03.017Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ec/286a5d2fad9c4bee59bd724feeb7d5bf8303c6c9200b51d1dd945a9c72b0/coverage-7.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11a7ec9f97ab950f4c5af62229befc7faf208fdbc0116d3902d7e306cf2c5abd", size = 252285, upload-time = "2026-06-22T23:09:04.773Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7d/a17753a0b12dd48d0d50f5fab079ad99d3be1eac790494d89f3a417ca0b9/coverage-7.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a571bd889cd36c5922ce8e42e059f9d37d02301531d11374afa4c87a578625d5", size = 254023, upload-time = "2026-06-22T23:09:06.513Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/a76c6ceba6a2c313f905310abf2701d534cada22d372db11731831e9e209/coverage-7.14.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de76caefc8deabb0dd1678b6a980be97d14c8d87e213ac194dbf8b09e96d63fb", size = 251989, upload-time = "2026-06-22T23:09:08.382Z" }, + { url = "https://files.pythonhosted.org/packages/d9/39/353013a75fec0fb49f7553519f9d52b4441e902e5178c93f38eb6c07cedb/coverage-7.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d20a15c622194234161535459affa8f7905830391c9ccfa060d495dbfe3a1c7f", size = 256144, upload-time = "2026-06-22T23:09:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/29/0e/613878555d734def11c5b20a2701a15cb3781b9e9ea749da27c5f436e928/coverage-7.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b488bd4b23397db62e7a9459129d01ff06a846582a732efd24834b24a6ada498", size = 251808, upload-time = "2026-06-22T23:09:12.057Z" }, + { url = "https://files.pythonhosted.org/packages/af/76/359c058c9cfdcf1e8b107663881225b03b364a320017eda24a2a66e55102/coverage-7.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a3693b4153394d265f44fb855fdc80e72403024d4d6f91c4871b334d028e4e0", size = 253579, upload-time = "2026-06-22T23:09:13.858Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d9/4ba2f060933a30ebe363cef9f67a365b0a317e580c0d5d9169d56a73ef1c/coverage-7.14.3-cp313-cp313-win32.whl", hash = "sha256:338b19131ab1a6b767b462bfcbaa692e7ae22f24463e39d49b02a83410ff6b37", size = 222741, upload-time = "2026-06-22T23:09:15.636Z" }, + { url = "https://files.pythonhosted.org/packages/76/e8/196ebc25d8f34c06d43a6e9c8513c9266ef8dbf3b5672beb1a00cf5e29fa/coverage-7.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:b3d77f7f196abdef7e01415de1bce09f216189e83e58159cfeef2b92d0464994", size = 223283, upload-time = "2026-06-22T23:09:17.478Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/51d2aac6417523a286f10fb25f09eb9518a84df9f1151e93ff6871f34849/coverage-7.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:e6230e688c7c3e65cedd41a774eb4ec221adc6bfee13768231015b702d5e4150", size = 222678, upload-time = "2026-06-22T23:09:19.7Z" }, + { url = "https://files.pythonhosted.org/packages/61/56/14e3b97facbfa1304dd19e676e26599ad359f04714bed32f7f1c5a88efdc/coverage-7.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:605ab2b566a22bd94834529d66d295c364aba84afd3e5498285c7a524017b1fc", size = 220741, upload-time = "2026-06-22T23:09:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/12/1d/db378b5cca433b90b893f26dab728b280ddd89f272a1fdfed4aeaa05c686/coverage-7.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3c2134809e80fac091bfed18a6991b5a5eb5df5ae32b17ac4f4f99864b73dd7", size = 221068, upload-time = "2026-06-22T23:09:23.452Z" }, + { url = "https://files.pythonhosted.org/packages/47/f0/3f8421b20d9c4fcd39be9a8ca3c3fda8bc204b44efbd09fede153afd3e2f/coverage-7.14.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c02efd507227bde9969cab0db8f48890eb3b5dcad6afac57a4792df4133543ce", size = 252117, upload-time = "2026-06-22T23:09:25.458Z" }, + { url = "https://files.pythonhosted.org/packages/27/ca/59ea35fb99743549ec8b37eff141ece4431fea590c89e536ed8032ef45cf/coverage-7.14.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1bb93c2aa61d2a5b38f1526546d95cf4132cb681e541a337bf8dfd092be816e5", size = 254622, upload-time = "2026-06-22T23:09:27.523Z" }, + { url = "https://files.pythonhosted.org/packages/c8/25/ec6de51ae7493b92a1cf74d1b763121c29636759167e2a593ba4db5881e4/coverage-7.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f502e948e03e866538048bba081c075caaa62e5bda6ea5b7432e45f587eb462a", size = 255968, upload-time = "2026-06-22T23:09:29.43Z" }, + { url = "https://files.pythonhosted.org/packages/5d/05/c8bfc77823f42b4664fb25842f13b567022f6f84a4c83c8ecbb16734b7cb/coverage-7.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9973ef2463f8e6cfb61a6324126bb3e17d67a85f22f58d856e583ea2e3ca6501", size = 258284, upload-time = "2026-06-22T23:09:31.397Z" }, + { url = "https://files.pythonhosted.org/packages/f6/15/1d1b242027124a32b26ef01f82018b8c4ef34ef174aa6aeba7b1eeef48e8/coverage-7.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9be4e7d4c5ca0427889f8f9d614bd630c2be741b1de7699bca3b2b6c0e41003e", size = 252143, upload-time = "2026-06-22T23:09:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/74/b6/d2a9842fd2a5d7d27f1ac851c043a734a494ad75402c5331db3da79ed691/coverage-7.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a574912f3bde4b0619f6e97d01aa590b70998859244793769eb3a6df78ee56d3", size = 253976, upload-time = "2026-06-22T23:09:35.351Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/e1600ddf7e226db5558bb5323d2186fff00f505c4b764643ec89ce5d8175/coverage-7.14.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e343fb086c9cd780b38622fea7c369acd64c1a0724312149b5d769c387a2b1f5", size = 251942, upload-time = "2026-06-22T23:09:37.313Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2c/9159de64f9dd648e324328d588a44cfab1e331eb5259ce1141afe2a92dfb/coverage-7.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3c68df8e61f1e09633fefc7538297145623957a048534368c9d212782aa5e845", size = 256220, upload-time = "2026-06-22T23:09:39.165Z" }, + { url = "https://files.pythonhosted.org/packages/91/67/b7f536cc2c124f48e91b22fbb741d2261f4e3d310faf6f76007f47566e5d/coverage-7.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3e5b550a128419373c2f6cec28a244207013ef15f5cbcff6a5ca09d1dfaaf027", size = 251756, upload-time = "2026-06-22T23:09:41.056Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ec/f3718038e2d4860c715a55428377ca7f6c75872caf98cabd982e1d76967d/coverage-7.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2bfc4dd0a912329eccc7484a7d0b2a38032b38c40663b1e1ac595f10c457954b", size = 253413, upload-time = "2026-06-22T23:09:43.306Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a5/91f11efeef89b3cc9b30461128db15b0511ef813ab889a7b7ab636b3a497/coverage-7.14.3-cp314-cp314-win32.whl", hash = "sha256:0423d64c013057a06e70f070f073cec4b0cbc7d2b27f3c7007292f2ff1d52965", size = 222946, upload-time = "2026-06-22T23:09:45.261Z" }, + { url = "https://files.pythonhosted.org/packages/58/fd/98ac9f524d9ec378de831c034dbdeb544ca7ef7d2d9c9996daf232a037fd/coverage-7.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:92c22e19ce64ca3f2ad751f16f14df1468b4c231bd6af97185063a9c292a0cb3", size = 223436, upload-time = "2026-06-22T23:09:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a0/7cd612d650a772a0ae80144443406bf61981c896c3d57c9e6e79fb2cdbd1/coverage-7.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:41de778bd41780586e2b04912079c73089ab5d839624e28db3bdb26de638da92", size = 222861, upload-time = "2026-06-22T23:09:49.384Z" }, + { url = "https://files.pythonhosted.org/packages/55/57/017353fab573779c0d00448e47d102edd36c792f7b6f233a4d89a7a08384/coverage-7.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8427f370ca67db4c975d2a26acfc0e5783ca0b52444dbc50278ace0f35445949", size = 221474, upload-time = "2026-06-22T23:09:51.417Z" }, + { url = "https://files.pythonhosted.org/packages/69/92/90cf1f1a5c468a9c1b7ba2716e0e205293ad9b02f5f573a6de4318b15ba1/coverage-7.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8e88f335544a47e22ae2e45b344772925ec65166555c958720d5ed971880891", size = 221738, upload-time = "2026-06-22T23:09:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c0/4df964fa539f8399fd7679c09c472d73744de334686fd3f01e3a2465ce4e/coverage-7.14.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:beaab199b9e5ceaf5a225e16a9d4df136f2a1eae0a5c20de1e277c8a5225f388", size = 263101, upload-time = "2026-06-22T23:09:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/06/76/e5d33b2576ae3bf2be2058cd1cae57774b61e400f2c3c58f3783dc2ffb4a/coverage-7.14.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ff255799f5a1676c71c1c32ec01fd043aa09d57b3d95764b24992757184784", size = 265225, upload-time = "2026-06-22T23:09:57.904Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/e52419afe391a39ba27fdefaf0737d8e34bf03faef6ab3b3006545bbd0d0/coverage-7.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:878832eaac515b62decfa76965aed558775f86bf1fc8cca76993c0c84ae31aed", size = 267643, upload-time = "2026-06-22T23:09:59.938Z" }, + { url = "https://files.pythonhosted.org/packages/58/7a/f2625d8d5006b6b20fba5afaef00b24a763fe96476ea798a3076cbc1f84e/coverage-7.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:611e62cb9386096d81b63e0a05330750268617231e7bd598e1fe77482a2c58a5", size = 268762, upload-time = "2026-06-22T23:10:01.943Z" }, + { url = "https://files.pythonhosted.org/packages/7d/bf/908024006bba57127354d74e938954b9c3cd765cc2e0412dc9c37b415cda/coverage-7.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:02c41de2a88011b893050fc9830267d927a50a215f7ad5ec17349db7090ccf26", size = 262208, upload-time = "2026-06-22T23:10:03.954Z" }, + { url = "https://files.pythonhosted.org/packages/34/a0/d4f9296441b909817442fdb26bd77a698f08272ec683a7394b00eb2e47a0/coverage-7.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:526ce9721116af23b1065089f0b75046fe521e7772ab94b641cd66b7a0421889", size = 265096, upload-time = "2026-06-22T23:10:05.936Z" }, + { url = "https://files.pythonhosted.org/packages/e8/da/4ae4f3f4e477b56a4ce1e5c48a35eff38a94b50130ce5bdc897024741cfc/coverage-7.14.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e4ed44705ca4bead6fc977a8b741f2145608289b33c8a9b42a95d0f15aedbf4d", size = 262699, upload-time = "2026-06-22T23:10:07.973Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7a/6927148073ff32856d78baa77b4ddc07a9be7e90020f9db0661c4ca523a1/coverage-7.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2415902f385a23dcc4ccd26e0ba803249a169af6a930c003a4c715eeb9a5444e", size = 266433, upload-time = "2026-06-22T23:10:10.145Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a7/774f658dbe9c4c3f5daa86a87e0459ac3832e4e3cc67affe078547f727b9/coverage-7.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b75ee850fc2d7c831e883220c445b035f2224de2ba6103f1e56dbd237ab913f7", size = 261547, upload-time = "2026-06-22T23:10:12.191Z" }, + { url = "https://files.pythonhosted.org/packages/3d/14/a0c18c0376c43cbf973f43ef6ca20019c950597180e6396232f7b6a27102/coverage-7.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dc9b4e35e7c3920e925ba7f14886fd5fbe481232754624e832ddba66c7535635", size = 263859, upload-time = "2026-06-22T23:10:14.492Z" }, + { url = "https://files.pythonhosted.org/packages/10/ac/43a3d0f460af524b131a6191805bc5d18b806ab4e828fbf82e8c8c3af446/coverage-7.14.3-cp314-cp314t-win32.whl", hash = "sha256:7b27c822a8161afbe48e99f1adfb098d270ae7e0f7d7b0555ce110529bdb69cc", size = 223250, upload-time = "2026-06-22T23:10:16.758Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5f/d5e5c56b0712e96ce8f69fe7dbf229ff938b437bc50862743c8a0d2cea84/coverage-7.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:39e1dbbb6ff2c338e0196a482558a792a1de3aa64261196f5cdb3da016ad9cda", size = 224082, upload-time = "2026-06-22T23:10:19.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/35/947cbd5be1d3bcbbdc43d6791de8a56c6501903311d42915ae06a82815f0/coverage-7.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:68520c90babfa2d560eca6d497921ed3a4f469623bd709733124491b2aa8ef3f", size = 223400, upload-time = "2026-06-22T23:10:21.24Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e3/a0aa32bfa3a081951f60a23bc0e7b512891ef0eecda1153cf1d8ba36c6b1/coverage-7.14.3-py3-none-any.whl", hash = "sha256:fb7e18afb6e903c1a92401a2f0501ac277dca527bb9ca6fe1f691a8a0026a0e8", size = 212469, upload-time = "2026-06-22T23:10:23.405Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "crewai" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appdirs" }, + { name = "chromadb" }, + { name = "click" }, + { name = "instructor" }, + { name = "json-repair" }, + { name = "json5" }, + { name = "jsonref" }, + { name = "mcp" }, + { name = "openai" }, + { name = "openpyxl" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-sdk" }, + { name = "pdfplumber" }, + { name = "portalocker" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt" }, + { name = "python-dotenv" }, + { name = "regex" }, + { name = "tokenizers" }, + { name = "tomli" }, + { name = "tomli-w" }, + { name = "uv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/c4/37f5e8e0ccb2804a3e2acc0ccf58f82dc9415a08fad71a3868cdf830c669/crewai-1.6.1.tar.gz", hash = "sha256:b7d73a8a333abf71b30ab20c54086004cd0c016dfd86bba9c035ad5eb31e22a7", size = 4177912, upload-time = "2025-11-29T01:58:25.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/87/8ab9924b79025165ed7f1b04a90f9b80137d18ceae9b8e34445a8495320c/crewai-1.6.1-py3-none-any.whl", hash = "sha256:8cec403ab89183bda28b830c722b6bc22457a2151a6aa46f07730e6fe7ab2723", size = 642861, upload-time = "2025-11-29T01:58:23.232Z" }, +] + +[package.optional-dependencies] +anthropic = [ + { name = "anthropic" }, +] +tools = [ + { name = "crewai-tools" }, +] + +[[package]] +name = "crewai-tools" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "crewai" }, + { name = "docker" }, + { name = "lancedb" }, + { name = "pymupdf" }, + { name = "python-docx" }, + { name = "pytube" }, + { name = "requests" }, + { name = "tiktoken" }, + { name = "youtube-transcript-api" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/e2/039d47c1d5266a807c9f4f8d4b927fab1ebfb60989ec6b65fcd88070a510/crewai_tools-1.6.1.tar.gz", hash = "sha256:8724400b85b0a97de09fe681b1d0bf4334e3e68bcf5ede8a056e2beed0227907", size = 805758, upload-time = "2025-11-29T01:58:29.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/d1/38d0bc01712cf086cd80fe778ed15ff9b57dc5cf4c0b11f1f40d88d4b50a/crewai_tools-1.6.1-py3-none-any.whl", hash = "sha256:62996840db257af9c471f9f4191b4268cb4dd7006b381670b968ac55973e08af", size = 764950, upload-time = "2025-11-29T01:58:27.441Z" }, +] + +[[package]] +name = "cryptography" +version = "49.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, + { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, + { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, + { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, + { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, + { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, + { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, + { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, + { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, + { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, + { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, + { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, + { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, + { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, + { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" }, + { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" }, + { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" }, + { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "deprecation" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "docker" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, +] + +[[package]] +name = "docstring-parser" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/4d/f332313098c1de1b2d2ff91cf2674415cc7cddab2ca1b01ae29774bd5fdf/docstring_parser-0.18.0.tar.gz", hash = "sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015", size = 29341, upload-time = "2026-04-14T04:09:19.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "durationpy" +version = "0.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/e44218c2b394e31a6dd0d6b095c4e1f32d0be54c2a4b250032d717647bab/durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba", size = 3335, upload-time = "2025-05-17T13:52:37.26Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, +] + +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + +[[package]] +name = "filelock" +version = "3.29.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, +] + +[[package]] +name = "flatbuffers" +version = "25.12.19" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { 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/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { 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/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { 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/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { 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/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { 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/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { 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/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { 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/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { 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" }, +] + +[[package]] +name = "fsspec" +version = "2026.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/a1/ae4e3e5003468d6391d2c77b6fa1cd73bd5d13511d81c642d7b28ac90ed4/fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a", size = 313646, upload-time = "2026-06-16T01:57:28.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, +] + +[[package]] +name = "furo" +version = "2025.12.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "beautifulsoup4" }, + { name = "pygments" }, + { name = "sphinx" }, + { name = "sphinx-basic-ng" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/20/5f5ad4da6a5a27c80f2ed2ee9aee3f9e36c66e56e21c00fde467b2f8f88f/furo-2025.12.19.tar.gz", hash = "sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7", size = 1661473, upload-time = "2025-12-19T17:34:40.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.75.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/c8/f439cffde755cffa462bfbb156278fa6f9d09119719af9814b858fd4f81f/googleapis_common_protos-1.75.0.tar.gz", hash = "sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd", size = 151035, upload-time = "2026-05-07T08:04:49.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/c8/e2645aa8ed02fd4c7a2f59d68783b65b1f3cbdfe39a6308e156509d1fee8/googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed", size = 300631, upload-time = "2026-05-07T08:03:30.345Z" }, +] + +[[package]] +name = "grpcio" +version = "1.81.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/b5/1ff353970a87eda4c98251e34d2dfd214abd4982dc89119c9252a2a482d2/grpcio-1.81.1.tar.gz", hash = "sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b", size = 13026582, upload-time = "2026-06-11T12:46:51.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/ea/1c2fa386b718ff493225e61cfc052ef400b4d6ffc54cbe261026432624b5/grpcio-1.81.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:d71d30f2d92f67d944631c523713934fee37292469e182ebcd2c1dd8a64ce53f", size = 6093112, upload-time = "2026-06-11T12:44:52.131Z" }, + { url = "https://files.pythonhosted.org/packages/2b/18/acf45fa8bd1bc5d7b0c2fd3dc4c209379fbd5bb396b440b68a83342226b7/grpcio-1.81.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b137f4bf3ada9dc44d411478decc6ff09a79ed30b306cd2abaa98408c3588137", size = 12074277, upload-time = "2026-06-11T12:44:55.354Z" }, + { url = "https://files.pythonhosted.org/packages/48/d7/ee86a60699b7db039f772a2c4a7e4facc7138984ff42c0130933a0063884/grpcio-1.81.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a3acb384427816dd5d470f47e62137b87f74da694faa8a50147012cf40df276a", size = 6640348, upload-time = "2026-06-11T12:44:59.223Z" }, + { url = "https://files.pythonhosted.org/packages/26/ee/d2de5e47378ffc207d476c230fea3be4d2601edbce9995f4fe45535d4896/grpcio-1.81.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f9a0ebbe45c29b5e5866593c12b78bd9035f0f0f0d4bc8361680cd580d99db49", size = 7331842, upload-time = "2026-06-11T12:45:02.001Z" }, + { url = "https://files.pythonhosted.org/packages/23/d6/abeda5c2b896a0b341584fe5ac411bbf72e197a9a374c355fb90965e08d2/grpcio-1.81.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a37165cc80b1a368384b383e63a4c38116a10467ae44c904d2d7468c4470ec2", size = 6842229, upload-time = "2026-06-11T12:45:04.76Z" }, + { url = "https://files.pythonhosted.org/packages/10/1c/1f0da7d590b4aeee006826ba568d0e419ca14b23e18f901a3da3e9fba613/grpcio-1.81.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6282caffb41ec326d4cb67ca9cf53b739d1b2f975a2acb498c7418e9f7d9a416", size = 7446096, upload-time = "2026-06-11T12:45:07.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/81/5c505d508f7c887aa7982d21443a4126597c80d34b0bcf40f9cec576d7f3/grpcio-1.81.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a35009284d0d3d5c2c9601c164a911b8b4331608d98a9a66d47d97bb2f522b70", size = 8445238, upload-time = "2026-06-11T12:45:10.243Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b2/524847365122ee509ca17bcc4e092198b700e94af7bfd5bb5e6dd9f3ee66/grpcio-1.81.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1b22c80559854b789a01fd89e8929b3798a156c0829b5282a8939f33ad4115ad", size = 7873989, upload-time = "2026-06-11T12:45:13.102Z" }, + { url = "https://files.pythonhosted.org/packages/18/fa/07c037c50b006909d1d13a5848774f8aa7b242f70dc03a035c64eea0e6db/grpcio-1.81.1-cp311-cp311-win32.whl", hash = "sha256:428bec0161b48d8cf583c068591bc0016d0d9cfff52462b72b3884861ea768c5", size = 4202223, upload-time = "2026-06-11T12:45:16.166Z" }, + { url = "https://files.pythonhosted.org/packages/41/ed/6bff15376920942fac6b95b9802752b837437172c9e8fc2d3170546b89cc/grpcio-1.81.1-cp311-cp311-win_amd64.whl", hash = "sha256:30e825f6848d9f18bba350ed6c75c1b02a0b5184474a31db9a32b1fa66fd8c79", size = 4941303, upload-time = "2026-06-11T12:45:18.724Z" }, + { url = "https://files.pythonhosted.org/packages/85/07/9a979c81738863a738dc23d65177056e71fbb2db817740ed870b33434e7a/grpcio-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115", size = 6053264, upload-time = "2026-06-11T12:45:21.017Z" }, + { url = "https://files.pythonhosted.org/packages/75/95/539706ca0d3bd40dbad583dc56fd883da941f37556b629132da5762781b9/grpcio-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3", size = 12052560, upload-time = "2026-06-11T12:45:23.652Z" }, + { url = "https://files.pythonhosted.org/packages/e0/44/f257b7e0bd69c93b06c6cb8ac8d1b901ccb42bedabd83c1a4c77a71f8810/grpcio-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2", size = 6595983, upload-time = "2026-06-11T12:45:26.963Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f3/19782aa04c960968bef8c5539329d8e3bbc3364e2e46d19eb5e5cc5e43b7/grpcio-1.81.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2c2e2ae6867c2966b8daccc836d54a13218e0007e9a490aeb81dd05be64d22d7", size = 7303455, upload-time = "2026-06-11T12:45:29.707Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8c/dea020b6d91508cd84463917a63149ec196ee7db505d032ae43fcb3303b9/grpcio-1.81.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:766bc7c9a9c340342f4c864ccbda8e78111e4751f13b895812b9c148fb79e9d0", size = 6809167, upload-time = "2026-06-11T12:45:32.52Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/3030dd940408083bd32cd95d634777a71605ade4887154d93e8a89244946/grpcio-1.81.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b259a04a737cb3496be0901328eb8b7552ed8df4865d8c8f1cf1bffcfc0776a3", size = 7412536, upload-time = "2026-06-11T12:45:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/e0/dd/1172a9e42b168edcafefad6115346ef619a3fc02158bb170e66ced24bcdd/grpcio-1.81.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:85b10a45b8993d195c4f3ff57025b8d1e11834909ee475c403bfa60cb4caefaf", size = 8408276, upload-time = "2026-06-11T12:45:37.78Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/71437c7f3596e5246155c515852795a85a1a8d228190212432b13b97a95d/grpcio-1.81.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8ea1936c26b99999b27479853039a7f34713f56c49375ad52b38535ec93a796c", size = 7849660, upload-time = "2026-06-11T12:45:40.627Z" }, + { url = "https://files.pythonhosted.org/packages/65/40/7debc0da45d2efebafb82da75644be347497fe4ee250514b8cd3b86ae8bf/grpcio-1.81.1-cp312-cp312-win32.whl", hash = "sha256:a185a04039df6cae8648bc8ab6d6fde7bf94f7188ecf7828e76ac52eef1e41d6", size = 4185819, upload-time = "2026-06-11T12:45:43.027Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b9/8fe3ba5ed462067774ebc1f9c7f26aa7ebcc280ddd476be107153de1339e/grpcio-1.81.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ad74f8bb1a18963914c5452d289422830b39459e8776ebbcd207be1fbfb1d94", size = 4930461, upload-time = "2026-06-11T12:45:45.775Z" }, + { url = "https://files.pythonhosted.org/packages/7a/42/dcc2e4b600538ef18327c0839d56b7d3c3812337c5d710df5877dbb39b1e/grpcio-1.81.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe", size = 6054466, upload-time = "2026-06-11T12:45:48.43Z" }, + { url = "https://files.pythonhosted.org/packages/7b/4a/a36e03210183a8a7d4c80c3936acee679f4bd77d5861f369db47b2cc5f05/grpcio-1.81.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e", size = 12048795, upload-time = "2026-06-11T12:45:54.011Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d5/d68e30b29098f63beab6fe501100fe82674ff142b32c672532da86a99b3a/grpcio-1.81.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0", size = 6599094, upload-time = "2026-06-11T12:45:57.799Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b3/e837954d279754f638a11cca5dcf6b24a005efb398984cefaf7735945a54/grpcio-1.81.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14", size = 7307182, upload-time = "2026-06-11T12:46:00.568Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/b47957057e729adc6cdf519a47f8be2562b7140e280f1418443eb4022192/grpcio-1.81.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae", size = 6810962, upload-time = "2026-06-11T12:46:03.312Z" }, + { url = "https://files.pythonhosted.org/packages/40/26/569868e364e05b19ec8f969da53d230bcd89c962cd198f7c29943155c4d3/grpcio-1.81.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5", size = 7415698, upload-time = "2026-06-11T12:46:06.005Z" }, + { url = "https://files.pythonhosted.org/packages/36/0c/5440a0582cb5653fc42a6e262eeb22700943313f8076f9dc927491b20a59/grpcio-1.81.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb", size = 8407779, upload-time = "2026-06-11T12:46:08.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/aa/66fe9f39871d766987d869a03ee0842a026f499c7b1e62decb9e78a8088e/grpcio-1.81.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7", size = 7844521, upload-time = "2026-06-11T12:46:12.171Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9e/69bb7194861bcd28fb3193261d4f9c3831b4446993f002cf59068943e7ab/grpcio-1.81.1-cp313-cp313-win32.whl", hash = "sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42", size = 4182786, upload-time = "2026-06-11T12:46:15.192Z" }, + { url = "https://files.pythonhosted.org/packages/0d/20/3da8bb0d637feccdc3e1e419bb511ce93651ce7d54164f95de22cc0b8b34/grpcio-1.81.1-cp313-cp313-win_amd64.whl", hash = "sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60", size = 4928648, upload-time = "2026-06-11T12:46:17.823Z" }, + { url = "https://files.pythonhosted.org/packages/b6/58/19414622b1bf6981bc9c05a365bd548e71876c89000083b3af489251e9c0/grpcio-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b", size = 6055336, upload-time = "2026-06-11T12:46:20.557Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/2ec88adb92b0eba970dd0e0e7dd086341daa3c75eba4f735f9e44bf684b0/grpcio-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e", size = 12056279, upload-time = "2026-06-11T12:46:24.255Z" }, + { url = "https://files.pythonhosted.org/packages/41/36/e8c5f8c6ec71de73733695ebc809e98b178b534ec6d8eaa31a7ebab4ad4c/grpcio-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27", size = 6608225, upload-time = "2026-06-11T12:46:27.601Z" }, + { url = "https://files.pythonhosted.org/packages/30/22/96fc577a845ab093326d9ab1adb874bd4936c8cf98ac8ed2f3db13a0a2fb/grpcio-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854", size = 7306576, upload-time = "2026-06-11T12:46:30.514Z" }, + { url = "https://files.pythonhosted.org/packages/76/7b/61dab5d5969f28d97fb1009cead1df0a5cd987d3315e1b37f18a4449f8bc/grpcio-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6", size = 6812165, upload-time = "2026-06-11T12:46:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/6e501929d4f5f96462fd82fd9f0f06e5f9612207582b862868d68757b27d/grpcio-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5", size = 7422962, upload-time = "2026-06-11T12:46:36.511Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7e/f2157589e66daa78ebb3165942d05a08bdea93b9d11c2bc1e172aef89685/grpcio-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0", size = 8408176, upload-time = "2026-06-11T12:46:39.803Z" }, + { url = "https://files.pythonhosted.org/packages/da/df/c6717fef716e00d235ffb96123baf6dce76d6004f6233fa767c502861460/grpcio-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190", size = 7846681, upload-time = "2026-06-11T12:46:43.013Z" }, + { url = "https://files.pythonhosted.org/packages/36/84/3502e9f210a6a5c4438c8aca3f88edd2e04f6a27f3d41b26cf0a0024b096/grpcio-1.81.1-cp314-cp314-win32.whl", hash = "sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f", size = 4264615, upload-time = "2026-06-11T12:46:45.741Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/4af731ff7492c68a96e4c71bfd0f4590acde92b31c6fe4894e6465c10ff6/grpcio-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821", size = 5070275, upload-time = "2026-06-11T12:46:48.486Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/2d/57fd21d84d93efb4bd0b962383790e19dd1bc053501b4264c97903b4e83e/hf_xet-1.5.1.tar.gz", hash = "sha256:51ef4500dab3764b41135ee1381a4b62ce56fc54d4c92b719b59e597d6df5bf6", size = 876636, upload-time = "2026-06-08T23:02:53.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/ee/dd9ba7beae1005e54131b7d45263cc74c8a066d47d354e6d58ae9445a388/hf_xet-1.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:dbf48c0d02cf0b2e568944330c60d9120c272dabe013bd892d48e25bc6797577", size = 4069485, upload-time = "2026-06-08T23:02:13.193Z" }, + { url = "https://files.pythonhosted.org/packages/b6/bc/9cae6cfeb4e03070874e73e5c97c66eb90369d3206b6a2b1ef5f96520888/hf_xet-1.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78e4e5192ad2b674c2e1160b651cb9134db974f8ae1835bdfbfb0166b894a43", size = 3838493, upload-time = "2026-06-08T23:02:15.282Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b4/d5c01e0eb6d9f2ca2dacd84d0d1b71e6cfbb2ef3208c968528e010e9b3d7/hf_xet-1.5.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6f7a04a8ad962422e225bc49fbbac99dc1806764b1f3e54dbd154bffa7593947", size = 4505658, upload-time = "2026-06-08T23:02:17.196Z" }, + { url = "https://files.pythonhosted.org/packages/76/c5/29a7598c0c6383c523dc22186d577f4e04267a626cd95ae60f67c00bfe66/hf_xet-1.5.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d48199c2bf4f8df0adc55d31d1368b6ec0e4d4f45bc86b08038089c23db0bed8", size = 4292822, upload-time = "2026-06-08T23:02:18.608Z" }, + { url = "https://files.pythonhosted.org/packages/04/9a/dceaf6ca69390126b86ea825fb354b93d01163199070b7bd849225de9468/hf_xet-1.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:97f212a88d14bbf573619a74b7fecb238de77d08fc702e54dec6f78276ca3283", size = 4491255, upload-time = "2026-06-08T23:02:20.124Z" }, + { url = "https://files.pythonhosted.org/packages/48/a7/e5a7afaacf6c1791fdbeeac42951fb81c3d2bc482992b115dedcc86d963e/hf_xet-1.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f61e3665892a6c8c5e765395838b8ddf36185da835253d4bc4509a81e49fb342", size = 4711062, upload-time = "2026-06-08T23:02:21.863Z" }, + { url = "https://files.pythonhosted.org/packages/53/49/2802f8433c9742ce281bddc1e65c02c32268ca3098d66828b05e12e45ee2/hf_xet-1.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f4ad3ebd4c32dd2b27099d69dc7b2df821e30767e46fb6ee6a0713778243b8ff", size = 4017205, upload-time = "2026-06-08T23:02:23.495Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5a/50c71195b9fb883659f596e7252faf4c18c58e753a9013bdbf9bac5d2250/hf_xet-1.5.1-cp313-cp313t-win_arm64.whl", hash = "sha256:8298485c1e36e7e67cbd01eeb1376619b7af43d4f1ec245caae306f890a8a32d", size = 3845426, upload-time = "2026-06-08T23:02:25.124Z" }, + { url = "https://files.pythonhosted.org/packages/05/24/5e0c28f80371c17d49fed004597d9d132cb75c1f6f53db2cb95f459d2312/hf_xet-1.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3474760d10e3bb6f92ff3f024fcb00c0b3e4001e9b035c7483e49a5dd17aa70f", size = 4069676, upload-time = "2026-06-08T23:02:26.759Z" }, + { url = "https://files.pythonhosted.org/packages/d2/17/261ba565b6a4d960fb478f61fdf919c0be5824645aaf1c319eca660c1611/hf_xet-1.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6762d89b9e3267dfd502b29b2a327b4525f33b17e7b509a78d94e2151a30ce30", size = 3838509, upload-time = "2026-06-08T23:02:28.573Z" }, + { url = "https://files.pythonhosted.org/packages/4e/44/7ffdc2e184b0d41fc0f683ba3936ef669ab63cf242cf36ef50e57d683668/hf_xet-1.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bf67e6ed10260cef62e852789dc91ebb03f382d5bdc4b1dbeb64763ea275e7d6", size = 4505881, upload-time = "2026-06-08T23:02:30.257Z" }, + { url = "https://files.pythonhosted.org/packages/63/b6/788060d5aa4d5e671f1a31bf69624c314eb2d8babab3aa562f9e5d53444e/hf_xet-1.5.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c6b6cd08ca095058780b50b8ce4d6cbf6787bcf27841705d58a9d32246e3e47a", size = 4292995, upload-time = "2026-06-08T23:02:31.993Z" }, + { url = "https://files.pythonhosted.org/packages/22/93/c5540cbd6b55529b7dc42f6734e88cebee21aefbea34128b66229df56c57/hf_xet-1.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e1af0de8ca6f190d4294a28b88023db64a1e2d1d719cab044baf75bec569e7a9", size = 4491570, upload-time = "2026-06-08T23:02:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/03/f3/9d8ceab30f44f36c1679b1b8683054c71a0dadc787dbf07421891742d3ca/hf_xet-1.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4f561cbbb92f80960772059864b7fb07eae879adde1b2e781ec6f86f6ac26c59", size = 4711565, upload-time = "2026-06-08T23:02:35.454Z" }, + { url = "https://files.pythonhosted.org/packages/cd/54/27ed9a5e2cc583b4df82f75a03a4df8dbf55f5a9fa1f47f1fadfb20dbeac/hf_xet-1.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e7dbb40617410f432182d918e37c12303fe6700fd6aa6c5964e30a535a4461d6", size = 4017343, upload-time = "2026-06-08T23:02:37.14Z" }, + { url = "https://files.pythonhosted.org/packages/ae/12/ecb2fc8d45e767580e3a37faa97cb895608b614965567efb4f18cff67e27/hf_xet-1.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6071d5ccb4d8d2cbd5fea5cc798da4f0ba3f44e25369591c4e89a4987050e61d", size = 3845716, upload-time = "2026-06-08T23:02:39.073Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d8/5e54cf37434759d1f4f2ba9b66077ff9d4c4e1f37b6bd7975da5c40d94ab/hf_xet-1.5.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6abd35c3221eff63836618ddfb954dcf84798603f71d8e33e3ed7b04acfdbe6e", size = 4077794, upload-time = "2026-06-08T23:02:40.656Z" }, + { url = "https://files.pythonhosted.org/packages/35/94/4b2ecfbad8f8b04701a23aefb62f540b9137d058b7e1dbef16a32676f0e9/hf_xet-1.5.1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:94e761bbd266bf4c03cee73753916062665ce8365aa40ed321f45afcb934b41e", size = 3845354, upload-time = "2026-06-08T23:02:42.702Z" }, + { url = "https://files.pythonhosted.org/packages/de/cc/f99f4bc7295023d7bd9ebbfd51f75cc530ca262c1227666268b8208f4b77/hf_xet-1.5.1-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:892e3a3a3aecc12aded8b93cf4f9cd059282c7de0732f7d55026f3abdf474350", size = 4514864, upload-time = "2026-06-08T23:02:44.497Z" }, + { url = "https://files.pythonhosted.org/packages/cd/6e/21f7e5a2381278bd3b7b7a5a4d90038518bb6308a0c1daf5d9f8268bb178/hf_xet-1.5.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a93df2039190502835b1db8cd7e178b0b7b889fe9ab51299d5ced26e0dd879a4", size = 4303784, upload-time = "2026-06-08T23:02:46.203Z" }, + { url = "https://files.pythonhosted.org/packages/35/0e/f992bb6927ac1cb30ef74e62268f551f338bc32b2191f7c96a44c6f7283e/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0c97106032ef70467b4f6bc2d0ccc266d7613ee076afc56516c502f87ce1c4a6", size = 4500703, upload-time = "2026-06-08T23:02:47.628Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d1/90a498d05447980b977b1669246eeeeae4cfb0ea3e7a286eaba627f91bf9/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6208adb15d192b90e4c2ad2a27ed864359b2cb0f2494eb6d7c7f3699ac02e2bf", size = 4719498, upload-time = "2026-06-08T23:02:49.268Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b6/20f99cfe97cc663a711f7b33cc21d4793e51968e9a26125b4afcd77315ba/hf_xet-1.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:f7b3002f95d1c13e24bcb4537baa8f0eb3838957067c91bb4959bc004a6435f5", size = 4026419, upload-time = "2026-06-08T23:02:50.829Z" }, + { url = "https://files.pythonhosted.org/packages/f9/fa/77453694888f03e5a8c8852d1514a0894d8e81c622d39edbaf308ea0dcf4/hf_xet-1.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:93d090b57b211133f6c0dab0205ef5cb6d89162979ba75a74845045cc3063b8e", size = 3855178, upload-time = "2026-06-08T23:02:52.452Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/d2/c3eedaef57de65c3cc5f8dc244cf12d09c84ad258a479055aad6db23206c/httptools-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168", size = 208428, upload-time = "2026-05-25T22:16:59.717Z" }, + { url = "https://files.pythonhosted.org/packages/f1/94/dfe435d90d0ef61ec0f2cc3d480eef78c59727c6c2ce039f433882f6131a/httptools-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d", size = 113366, upload-time = "2026-05-25T22:17:00.795Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d4/13025f1a56e615dcb331e0bbe2d9a1143212b58c263385fc5d2e558f5bac/httptools-0.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376", size = 464676, upload-time = "2026-05-25T22:17:02.014Z" }, + { url = "https://files.pythonhosted.org/packages/bf/95/4c1c26c0b985f8a3331682d802598f14e32dc41bf7509266eb2c04ad4801/httptools-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d", size = 464235, upload-time = "2026-05-25T22:17:03.109Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/6735be2b0ca527718c431cdb8e5f70c3862c0844a687df0f572c51e11497/httptools-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085", size = 449809, upload-time = "2026-05-25T22:17:04.443Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f9/5811c74f37a758c8a4aa3dc430375119d335947e883efc4664d8f3559a41/httptools-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124", size = 452174, upload-time = "2026-05-25T22:17:05.476Z" }, + { url = "https://files.pythonhosted.org/packages/cc/94/97b75870dea07b71e3ec535cebe525b08d723152e4c7d13fa887e51f4de2/httptools-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07", size = 90991, upload-time = "2026-05-25T22:17:06.75Z" }, + { url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" }, + { url = "https://files.pythonhosted.org/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" }, + { url = "https://files.pythonhosted.org/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" }, + { url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" }, + { url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" }, + { url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" }, + { url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" }, + { url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" }, + { url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" }, + { url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" }, + { url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typer" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/77/ce3331f40cb2d021fe9b24c46c41e72faf74493621138e5eddac12bf5e1c/huggingface_hub-1.21.0.tar.gz", hash = "sha256:a44f222cd8f2f7c2eade30b5e7a04cac984a3235fa61ea87a0a5a31db77d561f", size = 861572, upload-time = "2026-06-25T13:09:26.356Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/85/b505a99a133d9f99d21af182af416e9baef70bdeef019983479651e494c2/huggingface_hub-1.21.0-py3-none-any.whl", hash = "sha256:eadaa3678c512c82aea69e8675d90a184861e68de32f1105668628b4dce0e7cd", size = 721078, upload-time = "2026-06-25T13:09:24.402Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + +[[package]] +name = "importlib-resources" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/06/b56dfa750b44e86157093bc8fca0ab81dccbf5260510de4eaf1cb69b5b99/importlib_resources-7.1.0.tar.gz", hash = "sha256:0722d4c6212489c530f2a145a34c0a7a3b4721bc96a15fada5930e2a0b760708", size = 44985, upload-time = "2026-04-12T16:36:09.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/db/55a262f3606bebcae07cc14095338471ad7c0bbcaa37707e6f0ee49725b7/importlib_resources-7.1.0-py3-none-any.whl", hash = "sha256:1bd7b48b4088eddb2cd16382150bb515af0bd2c70128194392725f82ad2c96a1", size = 37232, upload-time = "2026-04-12T16:36:08.219Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "instructor" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "docstring-parser" }, + { name = "jinja2" }, + { name = "jiter" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "pydantic-core" }, + { name = "requests" }, + { name = "rich" }, + { name = "tenacity" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/5b/40d7906e46aa8607b7259fdfb0114865d87ba52e7554499d8f4830c16d63/instructor-1.15.3.tar.gz", hash = "sha256:2d13f1201a2a9c0a350a6286990f7609b29d04efd6da8f641354a110a3f8bb0b", size = 70049094, upload-time = "2026-06-15T05:51:17.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/b3/f999fa377b2e639b0b212216b9f493e72de8ed8ed64b803fb1fe6b776f99/instructor-1.15.3-py3-none-any.whl", hash = "sha256:12553651b77b933ac3e96e53c18d0d3e7c5e627d1ea35625c1e4e11be0d41c59", size = 252418, upload-time = "2026-06-15T05:51:21.428Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jiter" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/c1/0cddc6eb17d4c53a99840953f95dd3accdc5cfc7a337b0e9b26476276be9/jiter-0.14.0.tar.gz", hash = "sha256:e8a39e66dac7153cf3f964a12aad515afa8d74938ec5cc0018adcdae5367c79e", size = 165725, upload-time = "2026-04-10T14:28:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/1f/198ae537fccb7080a0ed655eb56abf64a92f79489dfbf79f40fa34225bcd/jiter-0.14.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7e791e247b8044512e070bd1f3633dc08350d32776d2d6e7473309d0edf256a2", size = 316896, upload-time = "2026-04-10T14:26:01.986Z" }, + { url = "https://files.pythonhosted.org/packages/cf/34/da67cff3fce964a36d03c3e365fb0f8726ade2a6cfd4d3c70107e216ead6/jiter-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71527ce13fd5a0c4e40ad37331f8c547177dbb2dd0a93e5278b6a5eecf748804", size = 321085, upload-time = "2026-04-10T14:26:03.364Z" }, + { url = "https://files.pythonhosted.org/packages/ed/36/4c72e67180d4e71a4f5dcf7886d0840e83c49ab11788172177a77570326e/jiter-0.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c4a7ab56f746014874f2c525584c0daca1dec37f66fd707ecef3b7e5c2228c", size = 347393, upload-time = "2026-04-10T14:26:05.314Z" }, + { url = "https://files.pythonhosted.org/packages/bc/db/9b39e09ceafa9878235c0fc29e3e3f9b12a4c6a98ea3085b998cadf3accc/jiter-0.14.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:376e9dafff914253bb9d46cdc5f7965607fbe7feb0a491c34e35f92b2770702e", size = 372937, upload-time = "2026-04-10T14:26:06.884Z" }, + { url = "https://files.pythonhosted.org/packages/b0/96/0dcba1d7a82c1b720774b48ef239376addbaf30df24c34742ac4a57b67b2/jiter-0.14.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23ad2a7a9da1935575c820428dd8d2490ce4d23189691ce33da1fc0a58e14e1c", size = 463646, upload-time = "2026-04-10T14:26:08.345Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e3/f61b71543e746e6b8b805e7755814fc242715c16f1dba58e1cbccb8032c2/jiter-0.14.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54b3ddf5786bc7732d293bba3411ac637ecfa200a39983166d1df86a59a43c9f", size = 380225, upload-time = "2026-04-10T14:26:10.161Z" }, + { url = "https://files.pythonhosted.org/packages/ad/5e/0ddeb7096aca099114abe36c4921016e8d251e6f35f5890240b31f1f60ae/jiter-0.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c001d5a646c2a50dc055dd526dad5d5245969e8234d2b1131d0451e81f3a373", size = 358682, upload-time = "2026-04-10T14:26:11.574Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d1/fe0c46cd7fda9cad8f1ff9ad217dc61f1e4280b21052ec6dfe88c1446ef2/jiter-0.14.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:834bb5bdabca2e91592a03d373838a8d0a1b8bbde7077ae6913fd2fc51812d00", size = 359973, upload-time = "2026-04-10T14:26:13.316Z" }, + { url = "https://files.pythonhosted.org/packages/ac/21/f5317f91729b501019184771c80d60abd89907009e7bfa6c7e348c5bdd44/jiter-0.14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4e9178be60e229b1b2b0710f61b9e24d1f4f8556985a83ff4c4f95920eea7314", size = 397568, upload-time = "2026-04-10T14:26:15.212Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/79d8f33fb2bf168db0df5c9cd16fe440a8ada57e929d3677b22712c2568f/jiter-0.14.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7e4ccff04ec03614e62c613e976a3a5860dc9714ce8266f44328bdc8b1cab2c", size = 522535, upload-time = "2026-04-10T14:26:16.956Z" }, + { url = "https://files.pythonhosted.org/packages/5c/00/d1e3ff3d2a465e67f08507d74bafb2dcd29eba91dc939820e39e8dea38b8/jiter-0.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69539d936fb5d55caf6ecd33e2e884de083ff0ea28579780d56c4403094bb8d9", size = 556709, upload-time = "2026-04-10T14:26:18.5Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/bbb2189f62ace8d95e869aa4c84c9946616f301e2d02895a6f20dcc3bba3/jiter-0.14.0-cp311-cp311-win32.whl", hash = "sha256:4927d09b3e572787cc5e0a5318601448e1ab9391bcef95677f5840c2d00eaa6d", size = 208660, upload-time = "2026-04-10T14:26:20.511Z" }, + { url = "https://files.pythonhosted.org/packages/b8/86/c500b53dcbf08575f5963e536ebd757a1f7c568272ba5d180b212c9a87fb/jiter-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:42d6ed359ac49eb922fdd565f209c57340aa06d589c84c8413e42a0f9ae1b842", size = 204659, upload-time = "2026-04-10T14:26:22.152Z" }, + { url = "https://files.pythonhosted.org/packages/75/4a/a676249049d42cb29bef82233e4fe0524d414cbe3606c7a4b311193c2f77/jiter-0.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:6dd689f5f4a5a33747b28686e051095beb214fe28cfda5e9fe58a295a788f593", size = 194772, upload-time = "2026-04-10T14:26:23.458Z" }, + { url = "https://files.pythonhosted.org/packages/5a/68/7390a418f10897da93b158f2d5a8bd0bcd73a0f9ec3bb36917085bb759ef/jiter-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2fb2ce3a7bc331256dfb14cefc34832366bb28a9aca81deaf43bbf2a5659e607", size = 316295, upload-time = "2026-04-10T14:26:24.887Z" }, + { url = "https://files.pythonhosted.org/packages/60/a0/5854ac00ff63551c52c6c89534ec6aba4b93474e7924d64e860b1c94165b/jiter-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5252a7ca23785cef5d02d4ece6077a1b556a410c591b379f82091c3001e14844", size = 315898, upload-time = "2026-04-10T14:26:26.601Z" }, + { url = "https://files.pythonhosted.org/packages/41/a1/4f44832650a16b18e8391f1bf1d6ca4909bc738351826bcc198bba4357f4/jiter-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c409578cbd77c338975670ada777add4efd53379667edf0aceea730cabede6fb", size = 343730, upload-time = "2026-04-10T14:26:28.326Z" }, + { url = "https://files.pythonhosted.org/packages/48/64/a329e9d469f86307203594b1707e11ae51c3348d03bfd514a5f997870012/jiter-0.14.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ede4331a1899d604463369c730dbb961ffdc5312bc7f16c41c2896415b1304a", size = 370102, upload-time = "2026-04-10T14:26:30.089Z" }, + { url = "https://files.pythonhosted.org/packages/94/c1/5e3dfc59635aa4d4c7bd20a820ac1d09b8ed851568356802cf1c08edb3cf/jiter-0.14.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92cd8b6025981a041f5310430310b55b25ca593972c16407af8837d3d7d2ca01", size = 461335, upload-time = "2026-04-10T14:26:31.911Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1b/dd157009dbc058f7b00108f545ccb72a2d56461395c4fc7b9cfdccb00af4/jiter-0.14.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:351bf6eda4e3a7ceb876377840c702e9a3e4ecc4624dbfb2d6463c67ae52637d", size = 378536, upload-time = "2026-04-10T14:26:33.595Z" }, + { url = "https://files.pythonhosted.org/packages/91/78/256013667b7c10b8834f8e6e54cd3e562d4c6e34227a1596addccc05e38c/jiter-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1dcfbeb93d9ecd9ca128bbf8910120367777973fa193fb9a39c31237d8df165", size = 353859, upload-time = "2026-04-10T14:26:35.098Z" }, + { url = "https://files.pythonhosted.org/packages/de/d9/137d65ade9093a409fe80955ce60b12bb753722c986467aeda47faf450ad/jiter-0.14.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:ae039aaef8de3f8157ecc1fdd4d85043ac4f57538c245a0afaecb8321ec951c3", size = 357626, upload-time = "2026-04-10T14:26:36.685Z" }, + { url = "https://files.pythonhosted.org/packages/2e/48/76750835b87029342727c1a268bea8878ab988caf81ee4e7b880900eeb5a/jiter-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7d9d51eb96c82a9652933bd769fe6de66877d6eb2b2440e281f2938c51b5643e", size = 393172, upload-time = "2026-04-10T14:26:38.097Z" }, + { url = "https://files.pythonhosted.org/packages/a6/60/456c4e81d5c8045279aefe60e9e483be08793828800a4e64add8fdde7f2a/jiter-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d824ca4148b705970bf4e120924a212fdfca9859a73e42bd7889a63a4ea6bb98", size = 520300, upload-time = "2026-04-10T14:26:39.532Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9f/2020e0984c235f678dced38fe4eec3058cf528e6af36ebf969b410305941/jiter-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff3a6465b3a0f54b1a430f45c3c0ba7d61ceb45cbc3e33f9e1a7f638d690baf3", size = 553059, upload-time = "2026-04-10T14:26:40.991Z" }, + { url = "https://files.pythonhosted.org/packages/ef/32/e2d298e1a22a4bbe6062136d1c7192db7dba003a6975e51d9a9eecabc4c2/jiter-0.14.0-cp312-cp312-win32.whl", hash = "sha256:5dec7c0a3e98d2a3f8a2e67382d0d7c3ac60c69103a4b271da889b4e8bb1e129", size = 206030, upload-time = "2026-04-10T14:26:42.517Z" }, + { url = "https://files.pythonhosted.org/packages/36/ac/96369141b3d8a4a8e4590e983085efe1c436f35c0cda940dd76d942e3e40/jiter-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:fc7e37b4b8bc7e80a63ad6cfa5fc11fab27dbfea4cc4ae644b1ab3f273dc348f", size = 201603, upload-time = "2026-04-10T14:26:44.328Z" }, + { url = "https://files.pythonhosted.org/packages/01/c3/75d847f264647017d7e3052bbcc8b1e24b95fa139c320c5f5066fa7a0bdd/jiter-0.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:ee4a72f12847ef29b072aee9ad5474041ab2924106bdca9fcf5d7d965853e057", size = 191525, upload-time = "2026-04-10T14:26:46Z" }, + { url = "https://files.pythonhosted.org/packages/97/2a/09f70020898507a89279659a1afe3364d57fc1b2c89949081975d135f6f5/jiter-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:af72f204cf4d44258e5b4c1745130ac45ddab0e71a06333b01de660ab4187a94", size = 315502, upload-time = "2026-04-10T14:26:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/d6/be/080c96a45cd74f9fce5db4fd68510b88087fb37ffe2541ff73c12db92535/jiter-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4b77da71f6e819be5fbcec11a453fde5b1d0267ef6ed487e2a392fd8e14e4e3a", size = 314870, upload-time = "2026-04-10T14:26:49.149Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5e/2d0fee155826a968a832cc32438de5e2a193292c8721ca70d0b53e58245b/jiter-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f4ea612fe8b84b8b04e51d0e78029ecf3466348e25973f953de6e6a59aa4c1", size = 343406, upload-time = "2026-04-10T14:26:50.762Z" }, + { url = "https://files.pythonhosted.org/packages/70/af/bf9ee0d3a4f8dc0d679fc1337f874fe60cdbf841ebbb304b374e1c9aaceb/jiter-0.14.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62fe2451f8fcc0240261e6a4df18ecbcd58327857e61e625b2393ea3b468aac9", size = 369415, upload-time = "2026-04-10T14:26:52.188Z" }, + { url = "https://files.pythonhosted.org/packages/0f/83/8e8561eadba31f4d3948a5b712fb0447ec71c3560b57a855449e7b8ddc98/jiter-0.14.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6112f26f5afc75bcb475787d29da3aa92f9d09c7858f632f4be6ffe607be82e9", size = 461456, upload-time = "2026-04-10T14:26:53.611Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c9/c5299e826a5fe6108d172b344033f61c69b1bb979dd8d9ddd4278a160971/jiter-0.14.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:215a6cb8fb7dc702aa35d475cc00ddc7f970e5c0b1417fb4b4ac5d82fa2a29db", size = 378488, upload-time = "2026-04-10T14:26:55.211Z" }, + { url = "https://files.pythonhosted.org/packages/5d/37/c16d9d15c0a471b8644b1abe3c82668092a707d9bedcf076f24ff2e380cd/jiter-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ab96a30fb3cb2c7e0cd33f7616c8860da5f5674438988a54ac717caccdbaa", size = 353242, upload-time = "2026-04-10T14:26:56.705Z" }, + { url = "https://files.pythonhosted.org/packages/58/ea/8050cb0dc654e728e1bfacbc0c640772f2181af5dedd13ae70145743a439/jiter-0.14.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:3a99c1387b1f2928f799a9de899193484d66206a50e98233b6b088a7f0c1edb2", size = 356823, upload-time = "2026-04-10T14:26:58.281Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/cf71506d270e5f84d97326bf220e47aed9b95e9a4a060758fb07772170ab/jiter-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ab18d11074485438695f8d34a1b6da61db9754248f96d51341956607a8f39985", size = 392564, upload-time = "2026-04-10T14:27:00.018Z" }, + { url = "https://files.pythonhosted.org/packages/b0/cc/8c6c74a3efb5bd671bfd14f51e8a73375464ca914b1551bc3b40e26ac2c9/jiter-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:801028dcfc26ac0895e4964cbc0fd62c73be9fd4a7d7b1aaf6e5790033a719b7", size = 520322, upload-time = "2026-04-10T14:27:01.664Z" }, + { url = "https://files.pythonhosted.org/packages/41/24/68d7b883ec959884ddf00d019b2e0e82ba81b167e1253684fa90519ce33c/jiter-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ad425b087aafb4a1c7e1e98a279200743b9aaf30c3e0ba723aec93f061bd9bc8", size = 552619, upload-time = "2026-04-10T14:27:03.316Z" }, + { url = "https://files.pythonhosted.org/packages/b6/89/b1a0985223bbf3150ff9e8f46f98fc9360c1de94f48abe271bbe1b465682/jiter-0.14.0-cp313-cp313-win32.whl", hash = "sha256:882bcb9b334318e233950b8be366fe5f92c86b66a7e449e76975dfd6d776a01f", size = 205699, upload-time = "2026-04-10T14:27:04.662Z" }, + { url = "https://files.pythonhosted.org/packages/4c/19/3f339a5a7f14a11730e67f6be34f9d5105751d547b615ef593fa122a5ded/jiter-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:9b8c571a5dba09b98bd3462b5a53f27209a5cbbe85670391692ede71974e979f", size = 201323, upload-time = "2026-04-10T14:27:06.139Z" }, + { url = "https://files.pythonhosted.org/packages/50/56/752dd89c84be0e022a8ea3720bcfa0a8431db79a962578544812ce061739/jiter-0.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:34f19dcc35cb1abe7c369b3756babf8c7f04595c0807a848df8f26ef8298ef92", size = 191099, upload-time = "2026-04-10T14:27:07.564Z" }, + { url = "https://files.pythonhosted.org/packages/91/28/292916f354f25a1fe8cf2c918d1415c699a4a659ae00be0430e1c5d9ffea/jiter-0.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e89bcd7d426a75bb4952c696b267075790d854a07aad4c9894551a82c5b574ab", size = 320880, upload-time = "2026-04-10T14:27:09.326Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c7/b002a7d8b8957ac3d469bd59c18ef4b1595a5216ae0de639a287b9816023/jiter-0.14.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b25beaa0d4447ea8c7ae0c18c688905d34840d7d0b937f2f7bdd52162c98a40", size = 346563, upload-time = "2026-04-10T14:27:11.287Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3b/f8d07580d8706021d255a6356b8fab13ee4c869412995550ce6ed4ddf97d/jiter-0.14.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:651a8758dd413c51e3b7f6557cdc6921faf70b14106f45f969f091f5cda990ea", size = 357928, upload-time = "2026-04-10T14:27:12.729Z" }, + { url = "https://files.pythonhosted.org/packages/47/5b/ac1a974da29e35507230383110ffec59998b290a8732585d04e19a9eb5ba/jiter-0.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e1a7eead856a5038a8d291f1447176ab0b525c77a279a058121b5fccee257f6f", size = 203519, upload-time = "2026-04-10T14:27:14.125Z" }, + { url = "https://files.pythonhosted.org/packages/96/6d/9fc8433d667d2454271378a79747d8c76c10b51b482b454e6190e511f244/jiter-0.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e692633a12cda97e352fdcd1c4acc971b1c28707e1e33aeef782b0cbf051975", size = 190113, upload-time = "2026-04-10T14:27:16.638Z" }, + { url = "https://files.pythonhosted.org/packages/4f/1e/354ed92461b165bd581f9ef5150971a572c873ec3b68a916d5aa91da3cc2/jiter-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:6f396837fc7577871ca8c12edaf239ed9ccef3bbe39904ae9b8b63ce0a48b140", size = 315277, upload-time = "2026-04-10T14:27:18.109Z" }, + { url = "https://files.pythonhosted.org/packages/a6/95/8c7c7028aa8636ac21b7a55faef3e34215e6ed0cbf5ae58258427f621aa3/jiter-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a4d50ea3d8ba4176f79754333bd35f1bbcd28e91adc13eb9b7ca91bc52a6cef9", size = 315923, upload-time = "2026-04-10T14:27:19.603Z" }, + { url = "https://files.pythonhosted.org/packages/47/40/e2a852a44c4a089f2681a16611b7ce113224a80fd8504c46d78491b47220/jiter-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce17f8a050447d1b4153bda4fb7d26e6a9e74eb4f4a41913f30934c5075bf615", size = 344943, upload-time = "2026-04-10T14:27:21.262Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1f/670f92adee1e9895eac41e8a4d623b6da68c4d46249d8b556b60b63f949e/jiter-0.14.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4f1c4b125e1652aefbc2e2c1617b60a160ab789d180e3d423c41439e5f32850", size = 369725, upload-time = "2026-04-10T14:27:22.766Z" }, + { url = "https://files.pythonhosted.org/packages/01/2f/541c9ba567d05de1c4874a0f8f8c5e3fd78e2b874266623da9a775cf46e0/jiter-0.14.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be808176a6a3a14321d18c603f2d40741858a7c4fc982f83232842689fe86dd9", size = 461210, upload-time = "2026-04-10T14:27:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a9/c31cbec09627e0d5de7aeaec7690dba03e090caa808fefd8133137cf45bc/jiter-0.14.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26679d58ba816f88c3849306dd58cb863a90a1cf352cdd4ef67e30ccf8a77994", size = 380002, upload-time = "2026-04-10T14:27:26.155Z" }, + { url = "https://files.pythonhosted.org/packages/50/02/3c05c1666c41904a2f607475a73e7a4763d1cbde2d18229c4f85b22dc253/jiter-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80381f5a19af8fa9aef743f080e34f6b25ebd89656475f8cf0470ec6157052aa", size = 354678, upload-time = "2026-04-10T14:27:27.701Z" }, + { url = "https://files.pythonhosted.org/packages/7d/97/e15b33545c2b13518f560d695f974b9891b311641bdcf178d63177e8801e/jiter-0.14.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:004df5fdb8ecbd6d99f3227df18ba1a259254c4359736a2e6f036c944e02d7c5", size = 358920, upload-time = "2026-04-10T14:27:29.256Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d2/8b1461def6b96ba44530df20d07ef7a1c7da22f3f9bf1727e2d611077bf1/jiter-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cff5708f7ed0fa098f2b53446c6fa74c48469118e5cd7497b4f1cd569ab06928", size = 394512, upload-time = "2026-04-10T14:27:31.344Z" }, + { url = "https://files.pythonhosted.org/packages/e3/88/837566dd6ed6e452e8d3205355afd484ce44b2533edfa4ed73a298ea893e/jiter-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:2492e5f06c36a976d25c7cc347a60e26d5470178d44cde1b9b75e60b4e519f28", size = 521120, upload-time = "2026-04-10T14:27:33.299Z" }, + { url = "https://files.pythonhosted.org/packages/89/6b/b00b45c4d1b4c031777fe161d620b755b5b02cdade1e316dcb46e4471d63/jiter-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7609cfbe3a03d37bfdbf5052012d5a879e72b83168a363deae7b3a26564d57de", size = 553668, upload-time = "2026-04-10T14:27:34.868Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d8/6fe5b42011d19397433d345716eac16728ac241862a2aac9c91923c7509a/jiter-0.14.0-cp314-cp314-win32.whl", hash = "sha256:7282342d32e357543565286b6450378c3cd402eea333fc1ebe146f1fabb306fc", size = 207001, upload-time = "2026-04-10T14:27:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/e5/43/5c2e08da1efad5e410f0eaaabeadd954812612c33fbbd8fd5328b489139d/jiter-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:bd77945f38866a448e73b0b7637366afa814d4617790ecd88a18ca74377e6c02", size = 202187, upload-time = "2026-04-10T14:27:38Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/6e39ac0b4cdfa23e606af5b245df5f9adaa76f35e0c5096790da430ca506/jiter-0.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:f2d4c61da0821ee42e0cdf5489da60a6d074306313a377c2b35af464955a3611", size = 192257, upload-time = "2026-04-10T14:27:39.504Z" }, + { url = "https://files.pythonhosted.org/packages/05/57/7dbc0ffbbb5176a27e3518716608aa464aee2e2887dc938f0b900a120449/jiter-0.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bf7ff85517dd2f20a5750081d2b75083c1b269cf75afc7511bdf1f9548beb3b", size = 323441, upload-time = "2026-04-10T14:27:41.039Z" }, + { url = "https://files.pythonhosted.org/packages/83/6e/7b3314398d8983f06b557aa21b670511ec72d3b79a68ee5e4d9bff972286/jiter-0.14.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8ef8791c3e78d6c6b157c6d360fbb5c715bebb8113bc6a9303c5caff012754a", size = 348109, upload-time = "2026-04-10T14:27:42.552Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4f/8dc674bcd7db6dba566de73c08c763c337058baff1dbeb34567045b27cdc/jiter-0.14.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e74663b8b10da1fe0f4e4703fd7980d24ad17174b6bb35d8498d6e3ebce2ae6a", size = 368328, upload-time = "2026-04-10T14:27:44.574Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/188e09a1f20906f98bbdec44ed820e19f4e8eb8aff88b9d1a5a497587ff3/jiter-0.14.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1aca29ba52913f78362ec9c2da62f22cdc4c3083313403f90c15460979b84d9b", size = 463301, upload-time = "2026-04-10T14:27:46.717Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f0/19046ef965ed8f349e8554775bb12ff4352f443fbe12b95d31f575891256/jiter-0.14.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b39b7d87a952b79949af5fef44d2544e58c21a28da7f1bae3ef166455c61746", size = 378891, upload-time = "2026-04-10T14:27:48.32Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c3/da43bd8431ee175695777ee78cf0e93eacbb47393ff493f18c45231b427d/jiter-0.14.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d918a68b26e9fab068c2b5453577ef04943ab2807b9a6275df2a812599a310", size = 360749, upload-time = "2026-04-10T14:27:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/72/26/e054771be889707c6161dbdec9c23d33a9ec70945395d70f07cfea1e9a6f/jiter-0.14.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:b08997c35aee1201c1a5361466a8fb9162d03ae7bf6568df70b6c859f1e654a4", size = 358526, upload-time = "2026-04-10T14:27:51.504Z" }, + { url = "https://files.pythonhosted.org/packages/c3/0f/7bea65ea2a6d91f2bf989ff11a18136644392bf2b0497a1fa50934c30a9c/jiter-0.14.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:260bf7ca20704d58d41f669e5e9fe7fe2fa72901a6b324e79056f5d52e9c9be2", size = 393926, upload-time = "2026-04-10T14:27:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/b1ff7d70deef61ac0b7c6c2f12d2ace950cdeecb4fdc94500a0926802857/jiter-0.14.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:37826e3df29e60f30a382f9294348d0238ef127f4b5d7f5f8da78b5b9e050560", size = 521052, upload-time = "2026-04-10T14:27:55.058Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7b/3b0649983cbaf15eda26a414b5b1982e910c67bd6f7b1b490f3cfc76896a/jiter-0.14.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:645be49c46f2900937ba0eaf871ad5183c96858c0af74b6becc7f4e367e36e06", size = 553716, upload-time = "2026-04-10T14:27:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/97/f8/33d78c83bd93ae0c0af05293a6660f88a1977caef39a6d72a84afab94ce0/jiter-0.14.0-cp314-cp314t-win32.whl", hash = "sha256:2f7877ed45118de283786178eceaf877110abacd04fde31efff3940ae9672674", size = 207957, upload-time = "2026-04-10T14:27:59.285Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ac/2b760516c03e2227826d1f7025d89bf6bf6357a28fe75c2a2800873c50bf/jiter-0.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:14c0cb10337c49f5eafe8e7364daca5e29a020ea03580b8f8e6c597fed4e1588", size = 204690, upload-time = "2026-04-10T14:28:00.962Z" }, + { url = "https://files.pythonhosted.org/packages/dc/2e/a44c20c58aeed0355f2d326969a181696aeb551a25195f47563908a815be/jiter-0.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5419d4aa2024961da9fe12a9cfe7484996735dca99e8e090b5c88595ef1951ff", size = 191338, upload-time = "2026-04-10T14:28:02.853Z" }, + { url = "https://files.pythonhosted.org/packages/32/a1/ef34ca2cab2962598591636a1804b93645821201cc0095d4a93a9a329c9d/jiter-0.14.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a25ffa2dbbdf8721855612f6dca15c108224b12d0c4024d0ac3d7902132b4211", size = 311366, upload-time = "2026-04-10T14:28:27.943Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/520576a532a6b8a6f42747afed289c8448c879a34d7802fe2c832d4fd38f/jiter-0.14.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ac9cbaa86c10996b92bd12c91659b60f939f8e28fcfa6bc11a0e90a774ce95b", size = 309873, upload-time = "2026-04-10T14:28:29.688Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7c/c16db114ea1f2f532f198aa8dc39585026af45af362c69a0492f31bc4821/jiter-0.14.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:844e73b6c56b505e9e169234ea3bdea2ea43f769f847f47ac559ba1d2361ebea", size = 344816, upload-time = "2026-04-10T14:28:31.348Z" }, + { url = "https://files.pythonhosted.org/packages/99/8f/15e7741ff19e9bcd4d753f7ff22f988fd54592f134ca13701c13ea8c20e0/jiter-0.14.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52c076f187405fc21523c746c04399c9af8ece566077ed147b2126f2bcba577", size = 351445, upload-time = "2026-04-10T14:28:33.093Z" }, + { url = "https://files.pythonhosted.org/packages/21/42/9042c3f3019de4adcb8c16591c325ec7255beea9fcd33a42a43f3b0b1000/jiter-0.14.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:fbd9e482663ca9d005d051330e4d2d8150bb208a209409c10f7e7dfdf7c49da9", size = 308810, upload-time = "2026-04-10T14:28:34.673Z" }, + { url = "https://files.pythonhosted.org/packages/60/cf/a7e19b308bd86bb04776803b1f01a5f9a287a4c55205f4708827ee487fbf/jiter-0.14.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:33a20d838b91ef376b3a56896d5b04e725c7df5bc4864cc6569cf046a8d73b6d", size = 308443, upload-time = "2026-04-10T14:28:36.658Z" }, + { url = "https://files.pythonhosted.org/packages/ca/44/e26ede3f0caeff93f222559cb0cc4ca68579f07d009d7b6010c5b586f9b1/jiter-0.14.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:432c4db5255d86a259efde91e55cb4c8d18c0521d844c9e2e7efcce3899fb016", size = 343039, upload-time = "2026-04-10T14:28:38.356Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/1f9ada30cef7b05e74bb06f52127e7a724976c225f46adb65c37b1dadfb6/jiter-0.14.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f00d94b281174144d6532a04b66a12cb866cbdc47c3af3bfe2973677f9861a", size = 349613, upload-time = "2026-04-10T14:28:40.066Z" }, +] + +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "json-repair" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/cb/50b0bbc3e504ef875aea0062cdc108077e4923fb8c1209c70c80dc043933/json_repair-0.25.2.tar.gz", hash = "sha256:161a56d7e6bbfd4cad3a614087e3e0dbd0e10d402dd20dc7db418432428cb32b", size = 20458, upload-time = "2024-06-27T16:26:15.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/43/ac6691c7b5aa7191c964a04ae926d2bb06d9297dba1f2287df5b85cb3715/json_repair-0.25.2-py3-none-any.whl", hash = "sha256:51d67295c3184b6c41a3572689661c6128cef6cfc9fb04db63130709adfc5bf0", size = 12740, upload-time = "2024-06-27T16:26:13.823Z" }, +] + +[[package]] +name = "json5" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/7d/05c46a96a78147ae3bf99c2f4169ce144a70220b8d6fcd56f6ec368b8ce9/json5-0.15.0.tar.gz", hash = "sha256:7424d1f1eb1d56da6e3d70643f53619862b4ce81440bdb8ecfd6f875e5ba4a71", size = 53278, upload-time = "2026-06-19T20:08:27.716Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/be/59527c99478aade6bb33a68d72e6e18dd4e6ff6eacfc7d01bdb15bc76912/json5-0.15.0-py3-none-any.whl", hash = "sha256:56636a30c0e8a4665fe2179c0212f32eae3796dea89ea6f649b9436ecdb39618", size = 36570, upload-time = "2026-06-19T20:08:26.748Z" }, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, +] + +[[package]] +name = "jsonref" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "kubernetes" +version = "36.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "certifi" }, + { name = "durationpy" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "requests-oauthlib" }, + { name = "six" }, + { name = "urllib3" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/57/8b538af5076bc3372949d76f70ba3449bdfe52f9e6488170fa5d4f7cbe70/kubernetes-36.0.2.tar.gz", hash = "sha256:03551fcb49cae1f708f63624041e37403545b7aaed10cbf54e2b01a37a5438e3", size = 2336738, upload-time = "2026-06-01T18:20:30.785Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/5c160dbdef7123f8cc97fd8ece7e0198627a426a2a49614845e9086feb8d/kubernetes-36.0.2-py2.py3-none-any.whl", hash = "sha256:faf9b5241b58de0c4a5069f2a0ffc8ac06fece7215156cd3d3ba081a78a858b6", size = 4617568, upload-time = "2026-06-01T18:20:28.737Z" }, +] + +[[package]] +name = "lance-namespace" +version = "0.8.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lance-namespace-urllib3-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/12/f7ab93b29be3edbf5fc3610714bf2d06088e7f4524bfb38dfd6852458b08/lance_namespace-0.8.6.tar.gz", hash = "sha256:18232e721c8188145f4ec9389cc2dfbeeabf54a619d94885ea1b3375bee9f4af", size = 11529, upload-time = "2026-06-12T17:36:41.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/1b/5b1668ee2dc8910965f390640359112a31157092fcf8e000b89c79b58708/lance_namespace-0.8.6-py3-none-any.whl", hash = "sha256:571eae34f9aad70e5b05020416c2860889b9ec82993ccd0eb015e7b39c3ea309", size = 13383, upload-time = "2026-06-12T17:36:43.456Z" }, +] + +[[package]] +name = "lance-namespace-urllib3-client" +version = "0.8.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/80/fb224b4a89c1c1638cde949cb6cce6c3aca7759effbfea46a3d9c3960b21/lance_namespace_urllib3_client-0.8.6.tar.gz", hash = "sha256:b6fb1d306e74a7576e5309919020be744527de484a63dbf5eed10f8b368548df", size = 228772, upload-time = "2026-06-12T17:36:42.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/90/1e27de15cd1b16785a1c7312beb0a59e75c8344a815f600f58173a565bd1/lance_namespace_urllib3_client-0.8.6-py3-none-any.whl", hash = "sha256:9d78249c3fb15aa3d15d668f78f04a275af3d08d800a7027492f37996ac4968b", size = 369950, upload-time = "2026-06-12T17:36:40.438Z" }, +] + +[[package]] +name = "lancedb" +version = "0.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecation" }, + { name = "lance-namespace" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "overrides", marker = "python_full_version < '3.12'" }, + { name = "packaging" }, + { name = "pyarrow" }, + { name = "pydantic" }, + { name = "tqdm" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/2f/d5a4b2a5bb1f800936c76a6d8a4daf127a86fcab621eeb70b574a5adc774/lancedb-0.33.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:d4eaf6fa7c2eac619208f1d396f4de635ee0f535673067118a31c1181575c48b", size = 48338115, upload-time = "2026-05-28T20:37:55.88Z" }, + { url = "https://files.pythonhosted.org/packages/07/12/31787b93a856b2c31382c7771dc22fb05575b70b87c9efe454269f4f0948/lancedb-0.33.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c6c2402ed2744245ae76c4167c0461da0a7a80f1608e0ec491c1548ea2b4302", size = 51162262, upload-time = "2026-05-28T20:37:59.101Z" }, + { url = "https://files.pythonhosted.org/packages/49/b7/081cc29f8e06bf12191b99ab3fe702aceebdb0914476b821a8c0445cacc8/lancedb-0.33.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ebf1ffad811e6254a93931a79489ba1f21f48564bdfa06abae846f5fcaaf3e8", size = 54381368, upload-time = "2026-05-28T20:38:02.2Z" }, + { url = "https://files.pythonhosted.org/packages/1c/bd/e0f4bd621f10ecf96a801b0166e87799ed7ca5a9dbabcef9a6c766a58ef3/lancedb-0.33.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:13da39f80adfea59e5831fe64e4166b2d70a2f843e6507bf644c4fe4c350087c", size = 51188986, upload-time = "2026-05-28T20:38:05.375Z" }, + { url = "https://files.pythonhosted.org/packages/d9/1a/a8647a432ac6aa59cdce1fc061a7050ea4278bcab364539b78af2ecf72d2/lancedb-0.33.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:21b712825f0a00225e8974a41352c4ea84b0899ef8c23b17f672fadc38bd8346", size = 54440958, upload-time = "2026-05-28T20:38:08.474Z" }, + { url = "https://files.pythonhosted.org/packages/08/6c/d0cc8da784cd7ed3b4940a5d1f3e7702e2d99a0a348ba81a376eed782810/lancedb-0.33.0-cp39-abi3-win_amd64.whl", hash = "sha256:4ba78c6202b0f6c2ce8edc7aa470e550d2da56271c7cbdd10428613f1f7126f9", size = 58751944, upload-time = "2026-05-28T20:38:11.549Z" }, +] + +[[package]] +name = "langchain-anthropic" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anthropic" }, + { name = "langchain-core" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/22/40ab129b08329ca295b391aa1d48267692b42594757084c6918e22b655ac/langchain_anthropic-1.4.8.tar.gz", hash = "sha256:c76891b2044d56105ff13c106ed12650637b53bd598a4bdf15b4796eefa2a4ec", size = 708524, upload-time = "2026-06-26T21:28:46.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/14/746235c4da89d9bc6a608c5f489f628e03feb8f697195c146e452c8f23c8/langchain_anthropic-1.4.8-py3-none-any.whl", hash = "sha256:778e9301b6fd517824f76ec1776975ce8add97a1f6a36c50ae3c2f4b03a66f7f", size = 52366, upload-time = "2026-06-26T21:28:45.535Z" }, +] + +[[package]] +name = "langchain-core" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpatch" }, + { name = "langchain-protocol" }, + { name = "langsmith" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "uuid-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/e3/bea6d0080acf183332f24dcd74c208aee5857cf8f783c3fb0bd86027d8fb/langchain_core-1.4.8.tar.gz", hash = "sha256:5bf1f8411077c904182ad8f975943d36adcbf579c4e017b3a118b719229ebf9a", size = 957974, upload-time = "2026-06-18T19:39:23.636Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/d6/bdf6f0481cc57ef300d6b1eb48cf1400c0409be715d6eb3cabadd1142a09/langchain_core-1.4.8-py3-none-any.whl", hash = "sha256:d84c28b05e3ba8d4271d0827aad5b592ccdaaf986e76768c23503f0a2045e8aa", size = 557416, upload-time = "2026-06-18T19:39:21.902Z" }, +] + +[[package]] +name = "langchain-protocol" +version = "0.0.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/59/b5959aea96faa9146e2e49a7a22882b3528c62efafe9a6a95beab30c2305/langchain_protocol-0.0.18.tar.gz", hash = "sha256:ec3e11782f1ed0c9db38e5a9ed01b0e7a0d3fba406faa8aef6594b73c56a63e6", size = 6150, upload-time = "2026-06-18T17:08:26.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/2e/d82db9eec13ad0f72e7aaad5c4bc730ab111934fdc83c85523206eb9b0a0/langchain_protocol-0.0.18-py3-none-any.whl", hash = "sha256:70b53a86fbf9cedc863555effe44da192ab02d556ddbf2cf95b8873adcf41b5a", size = 7221, upload-time = "2026-06-18T17:08:25.996Z" }, +] + +[[package]] +name = "langgraph" +version = "1.2.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, + { name = "langgraph-prebuilt" }, + { name = "langgraph-sdk" }, + { name = "pydantic" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/7a/ea09b05bb0cbddfa43bd34fc581357e87fc3f21a751cc0d419688c3106da/langgraph-1.2.6.tar.gz", hash = "sha256:f9b45a34f13930c94d96cdb76277447ad2cc70ec2d18cd2764d7fdadb36cdc1b", size = 714400, upload-time = "2026-06-18T20:58:21.514Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/32/772db1b00a9fe42f50320d1aa20caefb76e621eff1f7218b9918093d631d/langgraph-1.2.6-py3-none-any.whl", hash = "sha256:1cf94d3ca124f84f77ce408fa1b06c3dee680a8aafffe364a8fd5d7d03eb8695", size = 246132, upload-time = "2026-06-18T20:58:20.335Z" }, +] + +[[package]] +name = "langgraph-checkpoint" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "ormsgpack" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/47/886af6f886f0bff2273164a45f008694e48a96ff3cd25ff0228f2aa9480e/langgraph_checkpoint-4.1.1.tar.gz", hash = "sha256:6c2bdb530c91f91d7d9c1bd100925d0fc4f498d418c17f3587d1526279482a25", size = 184020, upload-time = "2026-05-22T16:57:38.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/b4/71425e3e38be92611300b9cc5e46a5bf98ab23f5ea8a75b73d02a2f1413c/langgraph_checkpoint-4.1.1-py3-none-any.whl", hash = "sha256:25d29144b082827218e7bc3f1e9b0566a4bb007895cd6cc26f66a8428739f56e", size = 56212, upload-time = "2026-05-22T16:57:37.203Z" }, +] + +[[package]] +name = "langgraph-prebuilt" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/66/ed9b93f56bc17ef22d551892f0ac2b225a97fe0fcf23a511b857f70d590b/langgraph_prebuilt-1.1.0.tar.gz", hash = "sha256:3c579cf6eed2d17f9c157c2d0fcaddcd8688524e7022d3b22b37a3bf4589d528", size = 178833, upload-time = "2026-05-12T03:37:49.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/43/3fe1a700b8490ed02679cdbbc8c915eb23a092faf496c9c1118abcd10be3/langgraph_prebuilt-1.1.0-py3-none-any.whl", hash = "sha256:51e311747d755b751d5c6b39b0c1446124d3a7643d2515017e6714b323508fc9", size = 41043, upload-time = "2026-05-12T03:37:48.007Z" }, +] + +[[package]] +name = "langgraph-sdk" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "langchain-core" }, + { name = "langchain-protocol" }, + { name = "orjson" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/2b/bd8ac26d4e97f6df88ef05ce5b6a38945a3903e1025d926f4752aa88aa97/langgraph_sdk-0.4.2.tar.gz", hash = "sha256:b88f0f5f6328ac0680d6790614a905b2bcfa257f2276dba4e38f0e86db0aa738", size = 348327, upload-time = "2026-06-01T17:51:19.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/05/aac507337cceae773c2cc9ab91eb6301963af7aeeb55b4217a00e15aff17/langgraph_sdk-0.4.2-py3-none-any.whl", hash = "sha256:75fa5096c1177ce39c847096a8fe3745ffd480ddb412995f836e9f5f884c43dd", size = 160521, upload-time = "2026-06-01T17:51:18.849Z" }, +] + +[[package]] +name = "langsmith" +version = "0.9.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "sniffio" }, + { name = "typing-extensions" }, + { name = "uuid-utils" }, + { name = "websockets" }, + { name = "xxhash" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/b7/43f80940a758ceb48714ccc10c7acb9564be4f0d4786e8ed5d46deb0be39/langsmith-0.9.3.tar.gz", hash = "sha256:868a007b3ecda002914b21212a953c77fcb1d71a8a09a94562c3b57941d3df3a", size = 4577380, upload-time = "2026-06-26T15:56:30.45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/fb/dc9f70b5581371fba7b01ba0704fe859bfcd12c4dad82b42ec12bd4160b1/langsmith-0.9.3-py3-none-any.whl", hash = "sha256:aac85686868a7e61d77858b880230e510583c61ea48ac0a197e6ceff64ffb22b", size = 619927, upload-time = "2026-06-26T15:56:28.301Z" }, +] + +[[package]] +name = "librt" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/87/2bf31fe17587b29e3f93ec31421e2b1e1c3e349b8bf6c7c313dbad1d5340/librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", size = 141092, upload-time = "2026-05-10T18:15:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/cf/08/5c5bf772920b7ebac6e32bc91a643e0ab3870199c0b542356d3baa83970a/librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", size = 142035, upload-time = "2026-05-10T18:15:36.242Z" }, + { url = "https://files.pythonhosted.org/packages/06/20/662a03d254e5b000d838e8b345d83303ddb768c080fd488e40634c0fa66b/librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", size = 475022, upload-time = "2026-05-10T18:15:37.56Z" }, + { url = "https://files.pythonhosted.org/packages/de/f3/aa81523e45184c6ec23dc7f63263362ec55f80a09d424c012359ecbe7e35/librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", size = 467273, upload-time = "2026-05-10T18:15:39.182Z" }, + { url = "https://files.pythonhosted.org/packages/6b/6f/59c74b560ca8853834d5501d589c8a2519f4184f273a085ffd0f37a1cc47/librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", size = 497083, upload-time = "2026-05-10T18:15:40.634Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7b/5aa4d2c9600a719401160bf7055417df0b2a47439b9d88286ce45e56b65f/librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", size = 489139, upload-time = "2026-05-10T18:15:41.934Z" }, + { url = "https://files.pythonhosted.org/packages/d6/31/9143803d7da6856a69153785768c4936864430eec0fd9461c3ea527d9922/librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", size = 508442, upload-time = "2026-05-10T18:15:43.206Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5a/bce08184488426bda4ccc2c4964ac048c8f68ae89bd7120082eef4233cfd/librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", size = 514230, upload-time = "2026-05-10T18:15:44.761Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/bb5e213d254b7505a0e658da199d8ab719086632ce09eef311ab27976523/librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", size = 494231, upload-time = "2026-05-10T18:15:46.308Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fb/541cdad5b1ab1300398c74c4c9a497b88e5074c21b1244c8f49731d3a284/librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", size = 537585, upload-time = "2026-05-10T18:15:47.629Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f2/464bb69295c320cb06bddb4f14a4ec67934ee14b2bffb12b19fb7ab287ba/librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", size = 100509, upload-time = "2026-05-10T18:15:49.157Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e7/a17ee1788f9e4fbf548c19f4afa07c92089b9e24fef6cb2410863781ef4c/librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", size = 118628, upload-time = "2026-05-10T18:15:50.345Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/6c766214f9f9903bcfcfbef97d807af8d8f5aa3502d247858ab17582d212/librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", size = 103122, upload-time = "2026-05-10T18:15:52.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, + { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, + { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, + { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, + { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, + { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, + { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" }, + { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" }, + { url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" }, + { url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, + { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, + { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, + { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, + { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, + { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, + { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, + { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, + { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, + { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, + { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, +] + +[[package]] +name = "lxml" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/3b/aab6728cae887456f409b4d75e8a01856e4f04bd510de38052a47768b680/lxml-6.1.1.tar.gz", hash = "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40", size = 4197430, upload-time = "2026-05-18T19:19:06.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/b0/83f481780d1548750b8ce2ec824073deef2f452d9cd1a6faff8507e3d16d/lxml-6.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2", size = 8526461, upload-time = "2026-05-18T19:17:25.862Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/30fa0f808002c7329397bfbb24e306789c0b29f04aa5842c07b174b4216f/lxml-6.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d", size = 4595375, upload-time = "2026-05-18T19:17:34.555Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d2/edb71cf0e561581a7c5eb2626244320eb04e9f8ce6d563184fd668b45073/lxml-6.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510", size = 4923654, upload-time = "2026-05-18T19:17:42.917Z" }, + { url = "https://files.pythonhosted.org/packages/4c/77/1bc7eeb0de4577d783fb625aa092cc9357883bba35845a3666bf1259f3dc/lxml-6.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a", size = 5067921, upload-time = "2026-05-18T19:17:49.175Z" }, + { url = "https://files.pythonhosted.org/packages/1b/3c/c0690d74bd2bc17bc03b5b0d093569ead597dd0bfa088bf99eef8c24e19c/lxml-6.1.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d", size = 5002456, upload-time = "2026-05-18T19:17:59.715Z" }, + { url = "https://files.pythonhosted.org/packages/66/8d/d1b3271af0c0f1e27e8472a849e4d2c65bc7766884b9ad2da9e76e145c88/lxml-6.1.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8", size = 5202776, upload-time = "2026-05-18T19:18:08.924Z" }, + { url = "https://files.pythonhosted.org/packages/7a/45/689824ffb237fd10125ad273f32b28ff04dc6203c2822c85ff65a93df65e/lxml-6.1.1-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009", size = 5329945, upload-time = "2026-05-18T19:18:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c0/ef73af53767e958fd87d437c170f272e2f6e6c0f854939f133a895f1e711/lxml-6.1.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6", size = 4659237, upload-time = "2026-05-18T19:18:18.657Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/e1158e40397585e91cb0472374a1f63d0926a1ddeaa92f13d1a1ffe306d5/lxml-6.1.1-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8", size = 5265904, upload-time = "2026-05-18T19:18:24.883Z" }, + { url = "https://files.pythonhosted.org/packages/a0/16/8687e5d1400ed1c0bc41dace232ebb7553952b618ea1f2e5fb6e2cfbbe23/lxml-6.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83", size = 5045225, upload-time = "2026-05-18T19:17:20.073Z" }, + { url = "https://files.pythonhosted.org/packages/ca/18/d877bd1ae2e5ffdfd4836565aba350db31feb2f2656d6ce70316ed66a05e/lxml-6.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6", size = 4712721, upload-time = "2026-05-18T19:17:40.512Z" }, + { url = "https://files.pythonhosted.org/packages/44/4d/1f44fd1d770b10dacbf6b5c6e520f4d6e0708744930f719dc04e67cab981/lxml-6.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c", size = 5252549, upload-time = "2026-05-18T19:17:51.236Z" }, + { url = "https://files.pythonhosted.org/packages/64/5d/1d66b84f850089254c230ef6ea6b267a5a54e2e179a5d960036a05d501d7/lxml-6.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08", size = 5226877, upload-time = "2026-05-18T19:18:00.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/00/84c4b5302d42a2d0184f38d538c8a197f33b52a50bd4f7bcfe990bce3036/lxml-6.1.1-cp311-cp311-win32.whl", hash = "sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621", size = 3594072, upload-time = "2026-05-18T19:17:12.714Z" }, + { url = "https://files.pythonhosted.org/packages/61/9d/2e2f7d876349f45e0f3e29f72da311668853d59b58d473a2dea4f0160135/lxml-6.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28", size = 4025469, upload-time = "2026-05-18T19:17:50.566Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d5/570e6390e4110331e6208b2ba83d1482cc9146808ee118b22824a34c1070/lxml-6.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b", size = 3667640, upload-time = "2026-05-19T19:22:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6e/c4add832b6fc1e887125b96f880d7b9b70aae5248718e046b1704bcac4b9/lxml-6.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7", size = 8570821, upload-time = "2026-05-18T19:17:42.068Z" }, + { url = "https://files.pythonhosted.org/packages/22/00/ff3009c88e65de8011630acf8ab5a09cb2becd2aaf47fba2f3449f6224e9/lxml-6.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1", size = 4624252, upload-time = "2026-05-18T19:17:47.897Z" }, + { url = "https://files.pythonhosted.org/packages/42/95/bb63f0fd62e554fe078e1fb3c8fe9083c14ddc7ad7fa178d10e57e071ac7/lxml-6.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc", size = 4930746, upload-time = "2026-05-18T19:18:29.637Z" }, + { url = "https://files.pythonhosted.org/packages/eb/99/0013e8d9b5960f4f041cf0b73e2f80c23eb5205b1f7bfb20203243651359/lxml-6.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc", size = 5093723, upload-time = "2026-05-18T19:18:34.168Z" }, + { url = "https://files.pythonhosted.org/packages/29/91/317b332636bfc7bddcff828d41b3307f50043f4b237e40849c333d80fa1a/lxml-6.1.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383", size = 5005557, upload-time = "2026-05-18T19:18:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/42/2f/cc9bf06afe70f9c9093ae60855d9759da9db601ec4080f7473319666ffd7/lxml-6.1.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b", size = 5631036, upload-time = "2026-05-18T19:18:44.858Z" }, + { url = "https://files.pythonhosted.org/packages/08/f6/af32e23e563971ffb0fb86be52bc5be5c2c118858ffc119bf6a9039b173d/lxml-6.1.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818", size = 5240367, upload-time = "2026-05-18T19:18:49.217Z" }, + { url = "https://files.pythonhosted.org/packages/78/83/8555d40948b09ce86f1bd0c68a7ac31d07b1929f92cc1b074006c97ef2d2/lxml-6.1.1-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740", size = 5350171, upload-time = "2026-05-18T19:18:52.779Z" }, + { url = "https://files.pythonhosted.org/packages/63/75/5d92da93729b7bad783689e6496049fa40927b45bec7bf183c981de3ca70/lxml-6.1.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f", size = 4694874, upload-time = "2026-05-18T19:18:55.139Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b5/3aad415a9a25b822e783f15deeb4dffccf5113030f1afa2222dd929313d9/lxml-6.1.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2", size = 5244492, upload-time = "2026-05-18T19:19:01.28Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a1/5fcf7eb9904b80086aa47dcf0027de07b1bb990afad2e6823144c368ae04/lxml-6.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635", size = 5048232, upload-time = "2026-05-18T19:18:12.67Z" }, + { url = "https://files.pythonhosted.org/packages/77/74/1f601b63c7a69fcdf10fa9b148c81da8442204194f6c55509cc485c786b9/lxml-6.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf", size = 4777023, upload-time = "2026-05-18T19:18:15.928Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b9/7a78f51aec95b1bf780d78e12705a9f6533284f8693dc5c0e6724fa53d3f/lxml-6.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc", size = 5645773, upload-time = "2026-05-18T19:18:23.223Z" }, + { url = "https://files.pythonhosted.org/packages/a5/6e/98a7b7ad54e4e74fa1f20fff776913980619d0ebe5558232d7da6580bdd8/lxml-6.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955", size = 5233088, upload-time = "2026-05-18T19:18:31.433Z" }, + { url = "https://files.pythonhosted.org/packages/65/d1/bc0ed2427bf609f2ee10da303a6a226f9c8bce94f945dc29a32ce55de6e4/lxml-6.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a", size = 5260995, upload-time = "2026-05-18T19:18:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/69/8b/6772e1a4b513fc50a8d931f19edde0e13ae6918510a1e13ff67864f3e5ed/lxml-6.1.1-cp312-cp312-win32.whl", hash = "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a", size = 3596382, upload-time = "2026-05-18T19:17:18.37Z" }, + { url = "https://files.pythonhosted.org/packages/1b/89/45198e9624762af2dfd2cb8782598477ceb29f6e59caab560388ae1f4ec1/lxml-6.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77", size = 3997255, upload-time = "2026-05-18T19:17:56.781Z" }, + { url = "https://files.pythonhosted.org/packages/90/a9/7a54b6834088d9ae528a7b780584ba6a39a9457b0ac330479f20ffbc9449/lxml-6.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f", size = 3659610, upload-time = "2026-05-19T19:22:50.843Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/7e6f37c5584ccbb2ff267f56fd0339016938c1c8684cfefab9b33ffc2f36/lxml-6.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736", size = 8559780, upload-time = "2026-05-18T19:17:57.661Z" }, + { url = "https://files.pythonhosted.org/packages/a1/36/587c2521cf23a2cd6c9c22108aa7528f683a1f195ed7ccd23a4b1786ad36/lxml-6.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9", size = 4618006, upload-time = "2026-05-18T19:18:04.452Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ca/ab7bfe2bf4c972af5e7878262845ead3a24a929a9b04bc11c7c1ece6c82a/lxml-6.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354", size = 4924139, upload-time = "2026-05-18T19:19:04.873Z" }, + { url = "https://files.pythonhosted.org/packages/6b/55/a0c72851dfee5ecc689f949723a73dea457758912542cb955b108eaf0d8f/lxml-6.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca", size = 5082329, upload-time = "2026-05-18T19:19:09.728Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b6/0608f7d61a3b96cc67e5648a3d906e31a5082093e10e7be65b3886289938/lxml-6.1.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099", size = 4993564, upload-time = "2026-05-18T19:19:13.608Z" }, + { url = "https://files.pythonhosted.org/packages/4c/66/ae227524b066d29d55bf0b453d93d2d793c40218657d643dcbbca13b8faf/lxml-6.1.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6", size = 5613467, upload-time = "2026-05-18T19:19:16.228Z" }, + { url = "https://files.pythonhosted.org/packages/a6/76/dbe4a00b50385e40194231dcfe5a12c059de7cf90e89c83407d2b085b719/lxml-6.1.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085", size = 5228304, upload-time = "2026-05-18T19:19:19.354Z" }, + { url = "https://files.pythonhosted.org/packages/1c/01/00b1b8442ed2041793336868ba0b9ea4b13d7da7c085c6404c207a63bf79/lxml-6.1.1-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e", size = 5341607, upload-time = "2026-05-18T19:19:22.297Z" }, + { url = "https://files.pythonhosted.org/packages/63/36/1ad29931e9a4638bb707869f01d423a6c815f82152138d1a40dfcfde2b95/lxml-6.1.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f", size = 4700168, upload-time = "2026-05-18T19:19:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d1/a9536cecf9be18a0dc72d32bead283a2332d1ffebd2dd3ac70ce444686e5/lxml-6.1.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c", size = 5232487, upload-time = "2026-05-18T19:19:28.603Z" }, + { url = "https://files.pythonhosted.org/packages/0e/77/b4fb1e03bf5d130e879214d3100092e386418807fb74dd0adc4b0a48f351/lxml-6.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b", size = 5044231, upload-time = "2026-05-18T19:18:42.246Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/d00daeeb0a5530c4028a9232aa1b93db3ef4ed2158c116ea73c79a9765b3/lxml-6.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2", size = 4769450, upload-time = "2026-05-18T19:18:48.013Z" }, + { url = "https://files.pythonhosted.org/packages/ed/6a/715a3a8d156ce42f29cf014706f5410c2ff3b02267774110fc23266409fe/lxml-6.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5", size = 5635874, upload-time = "2026-05-18T19:18:51.914Z" }, + { url = "https://files.pythonhosted.org/packages/45/37/0544bc21dde2a88f3a17b504e6fc79c0e01d25a33c2f6079724e9e72b9c7/lxml-6.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785", size = 5223987, upload-time = "2026-05-18T19:18:59.715Z" }, + { url = "https://files.pythonhosted.org/packages/4d/f8/f6a5e8185bcb28c2befae3d31f8e3df3b811cb0f47746517a81279fcafe1/lxml-6.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947", size = 5250276, upload-time = "2026-05-18T19:19:03.834Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f2/1a2b9f1b7a49d45495369be7ef9ad05b262930f2eab3e3145706fca8083f/lxml-6.1.1-cp313-cp313-win32.whl", hash = "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca", size = 3596903, upload-time = "2026-05-18T19:17:29.863Z" }, + { url = "https://files.pythonhosted.org/packages/e6/99/f4ffb024f238eec2131aaa09f3278fb6129cf892741bf68e1fc1afb8c100/lxml-6.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660", size = 3995869, upload-time = "2026-05-18T19:18:02.596Z" }, + { url = "https://files.pythonhosted.org/packages/d1/53/70eb8c5c6037f27448f1e3c54ebede9545a801ae63f0a7254afca4fe8e45/lxml-6.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc", size = 3658490, upload-time = "2026-05-19T19:22:53.846Z" }, + { url = "https://files.pythonhosted.org/packages/13/e2/2e325795566de01d0d7c3bb57d3c370616b2d07b01214e84eec5d3b10963/lxml-6.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0", size = 8577146, upload-time = "2026-05-18T19:18:17.765Z" }, + { url = "https://files.pythonhosted.org/packages/93/cf/5630b5e4be7d2e6bee8efe83865c925221103cf0221303b104ce134b01e2/lxml-6.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840", size = 4623866, upload-time = "2026-05-18T19:18:30.669Z" }, + { url = "https://files.pythonhosted.org/packages/d2/51/3904907c063451cf8d4a5c9fe0cad95fa1f4ec57f4e3884fa0731bd7a305/lxml-6.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14", size = 4950022, upload-time = "2026-05-18T19:19:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/94/cd/9c7611a51c37a2830928405817cc5d56a97f64fab83cc3f628748b135749/lxml-6.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909", size = 5086695, upload-time = "2026-05-18T19:19:34.764Z" }, + { url = "https://files.pythonhosted.org/packages/da/d6/24e3b5906abb0b674ff2ae195bc3ce59708df2bcd17cf17703b2d7dd643a/lxml-6.1.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00", size = 5031642, upload-time = "2026-05-18T19:19:37.771Z" }, + { url = "https://files.pythonhosted.org/packages/2d/db/6ec54f99019838bff54785c51da07f189eb4676861c5f2730962b0d8d665/lxml-6.1.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955", size = 5647338, upload-time = "2026-05-18T19:19:40.553Z" }, + { url = "https://files.pythonhosted.org/packages/42/3d/ef4dcfffd22d27a61805d8ed9f7fb888495bc6aa88648fa07c1eaa5586b6/lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13", size = 5239528, upload-time = "2026-05-18T19:19:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/62/bb/37fb3f0dff146bdcfa78eec47879273820b2a0bf350ec236ce14bd0b1c26/lxml-6.1.1-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7", size = 5350730, upload-time = "2026-05-18T19:19:46.307Z" }, + { url = "https://files.pythonhosted.org/packages/90/42/43253f168388df4fae1f38c01df36ddb9bee39e2048167b54cdcbae85ea3/lxml-6.1.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245", size = 4697530, upload-time = "2026-05-18T19:19:49.889Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a8/c5a8504f81bbdfc8e7094c2c850cdb4ed6777fc4d5ddd9e5ab819f3b0d54/lxml-6.1.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5", size = 5250670, upload-time = "2026-05-18T19:19:53.199Z" }, + { url = "https://files.pythonhosted.org/packages/77/b7/c7e76ab18744d75e21f320ebf9ff9d1ceae2b54dd431ea5a64caf26c9672/lxml-6.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462", size = 5084485, upload-time = "2026-05-18T19:19:08.422Z" }, + { url = "https://files.pythonhosted.org/packages/31/31/b35c53f8ef7b7c31cacd23d3638652fff7bcd1deb6eedb709ab43b685908/lxml-6.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465", size = 4737635, upload-time = "2026-05-18T19:19:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/d9/06/31f23c813a7fe8e0cb1b175e915b08c9bf4e86d225b210feadbdbe519667/lxml-6.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a", size = 5670681, upload-time = "2026-05-18T19:19:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bc/ce619bccc89b1fd9ad8a8e1330ee3f3beff9f2ff95b712d7bbcdd6e22fc3/lxml-6.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590", size = 5238229, upload-time = "2026-05-18T19:19:18.131Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5d/b329acbbedc0b619ebc2be6cf7ee9ed07e80892c88d4dfd612c33805789a/lxml-6.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb", size = 5264191, upload-time = "2026-05-18T19:19:21.118Z" }, + { url = "https://files.pythonhosted.org/packages/d6/85/be36fb1425b30db3c3f9df75fe86343ebffb79e6320bd7f588e25bfeac39/lxml-6.1.1-cp314-cp314-win32.whl", hash = "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603", size = 3657202, upload-time = "2026-05-18T19:17:39.509Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ce/3cf9a827342269f54d405a6202397de63f07c69cbd6ce7d183a3f0cba1e9/lxml-6.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137", size = 4064497, upload-time = "2026-05-18T19:18:14.662Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3e/1a957bde8f0760039e627f94699f82caa782c9d838d86c3d28245ee67212/lxml-6.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf", size = 3741991, upload-time = "2026-05-19T19:22:59.111Z" }, + { url = "https://files.pythonhosted.org/packages/78/b2/00ed55b3a2efa4658fb795c38d1090ec9b3e8a6c3683d4441fa517f09c3b/lxml-6.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee", size = 8827545, upload-time = "2026-05-18T19:18:41.193Z" }, + { url = "https://files.pythonhosted.org/packages/c0/73/74573db19baa618d5f266f2407898b087ff6927115b00b71e5fc1b700847/lxml-6.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c", size = 4735736, upload-time = "2026-05-18T19:18:46.761Z" }, + { url = "https://files.pythonhosted.org/packages/16/02/6f7061f4f95f51e545d48e87647c54791d204a4e881be4156e7a26ba5338/lxml-6.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef", size = 4970291, upload-time = "2026-05-18T19:19:56.215Z" }, + { url = "https://files.pythonhosted.org/packages/b0/02/55fc057d8283427dea7d6edb102e7a840239c77a64a983d92f62a304c0e9/lxml-6.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a", size = 5102822, upload-time = "2026-05-18T19:19:59.223Z" }, + { url = "https://files.pythonhosted.org/packages/e4/48/8e1cf78d89d66850121d9255a2a24414c98f775da93b90cf976956c24b14/lxml-6.1.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c", size = 5027923, upload-time = "2026-05-18T19:20:01.549Z" }, + { url = "https://files.pythonhosted.org/packages/ed/00/0632a0647612c8af24d26997b3b961397daa9d5b2581444805933629a4cb/lxml-6.1.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525", size = 5595843, upload-time = "2026-05-18T19:20:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/bc/86/ab008a7dc360711b66858d61c80a5979a70a09f2aa2b05d9698df80b803d/lxml-6.1.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca", size = 5224515, upload-time = "2026-05-18T19:20:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/75/c6/2702ff375e728e34f56d9a45339a9cf7e4427e917f542225242d63a05afa/lxml-6.1.1-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e", size = 5312511, upload-time = "2026-05-18T19:20:09.308Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/a5807c98f87a86f10ef9ffab35516df7c0f0c4b6d5d33e9f608ab9c04a31/lxml-6.1.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038", size = 4639206, upload-time = "2026-05-18T19:20:11.704Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e1/8a0a2c35734812395f4da4eaf33748a7e5705bfb2a58b128da764339d5ec/lxml-6.1.1-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e", size = 5232404, upload-time = "2026-05-18T19:20:14.064Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e2/0e6a4dd5ad84d01d99aa7bae7cfefd4a760a0e0f8176818241de17d9b6c0/lxml-6.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072", size = 5083769, upload-time = "2026-05-18T19:19:23.758Z" }, + { url = "https://files.pythonhosted.org/packages/a0/7e/161f33d463f6ffc1c7679104b65086dea120080d49dde4d238f015aaee2f/lxml-6.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52", size = 4758936, upload-time = "2026-05-18T19:19:27.256Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fb/2369825e3f6ca99305bf9f7b7085fda91c8b0922a89e54d900974aa3ef85/lxml-6.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b", size = 5620296, upload-time = "2026-05-18T19:19:29.993Z" }, + { url = "https://files.pythonhosted.org/packages/30/90/d61e383146f74c5ab683947ea14dc7b82778838ab9b95ea73a23b60d0191/lxml-6.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2", size = 5228598, upload-time = "2026-05-18T19:19:33.523Z" }, + { url = "https://files.pythonhosted.org/packages/76/2d/2dafd8149e94b05bb070690efd5bb2680720681e03ff03fc57d2b70a1105/lxml-6.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e", size = 5247845, upload-time = "2026-05-18T19:19:36.649Z" }, + { url = "https://files.pythonhosted.org/packages/ce/68/b30e913340c380ddac9580c6e6230991fc37240ec4f64704833e4f3e2769/lxml-6.1.1-cp314-cp314t-win32.whl", hash = "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1", size = 3897345, upload-time = "2026-05-18T19:17:33.562Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4e/9eb2af5335545f9fbcd7af57bcf87c6025d31eaa31b14ec184a6c8675328/lxml-6.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e", size = 4393350, upload-time = "2026-05-18T19:18:10.076Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2c/0f1e93c636720e8a3eb59af2bfda99d98b55891e1c53bc30c2e0e865f01b/lxml-6.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c", size = 3817223, upload-time = "2026-05-19T19:22:56.823Z" }, + { url = "https://files.pythonhosted.org/packages/b5/32/86a3f0f724a3a402d4627937a7fc27b160e45e7012b4adf47f6e1e844511/lxml-6.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e", size = 3930127, upload-time = "2026-05-18T19:19:02.27Z" }, + { url = "https://files.pythonhosted.org/packages/40/44/d832e82af08723761556d004b1d04d281c09f9a8cecd7d3148548c9941a3/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004", size = 4210769, upload-time = "2026-05-18T19:20:41.427Z" }, + { url = "https://files.pythonhosted.org/packages/6d/39/0dc5949f759ed7d951e0bb8c2f2d9d7aca1908d22352fa84a8afd2ea54af/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e", size = 4318163, upload-time = "2026-05-18T19:20:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/e6/fb/8ab3845fe046ba4cbf74536bcf6801a774b7caf4350de1c5d37f1f0a9e90/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2", size = 4250945, upload-time = "2026-05-18T19:20:47.385Z" }, + { url = "https://files.pythonhosted.org/packages/68/1b/7553ab136894374ffae8851ec06f98f511cd8e66246e41b6be059d0a7289/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf", size = 4401664, upload-time = "2026-05-18T19:20:50.489Z" }, + { url = "https://files.pythonhosted.org/packages/db/a4/441aee36c6f6b249823d20fd91f9be9ab89d7c5a8ae542a4a4ca6d342d56/lxml-6.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84", size = 3508989, upload-time = "2026-05-18T19:18:38.158Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { 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/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { 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/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { 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/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mcp" +version = "1.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/77/9450b8f251a13affb6281997d0523c4615f8a8b35d0b21ff30db3a5aac9d/mcp-1.28.1.tar.gz", hash = "sha256:d51e36a5f5644faea4f85ea649bfffa6bc6c26770d42798ad6a3de3d2ba69683", size = 638501, upload-time = "2026-06-26T12:57:29.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/5e/d118fce19f87a2e7d8101c35c8ae0ec289098a4df0ff244cec23e415aca0/mcp-1.28.1-py3-none-any.whl", hash = "sha256:2726bca5e7193f61c5dde8b12500a6de2d9acf6d1a1c0be9e8c2e706437991df", size = 222620, upload-time = "2026-06-26T12:57:27.218Z" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl", hash = "sha256:214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d", size = 66663, upload-time = "2026-05-13T09:03:37.76Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mmh3" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/1a/edb23803a168f070ded7a3014c6d706f63b90c84ccc024f89d794a3b7a6d/mmh3-5.2.1.tar.gz", hash = "sha256:bbea5b775f0ac84945191fb83f845a6fd9a21a03ea7f2e187defac7e401616ad", size = 33775, upload-time = "2026-03-05T15:55:57.716Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/d7/3312a59df3c1cdd783f4cf0c4ee8e9decff9c5466937182e4cc7dbbfe6c5/mmh3-5.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dae0f0bd7d30c0ad61b9a504e8e272cb8391eed3f1587edf933f4f6b33437450", size = 56082, upload-time = "2026-03-05T15:53:59.702Z" }, + { url = "https://files.pythonhosted.org/packages/61/96/6f617baa098ca0d2989bfec6d28b5719532cd8d8848782662f5b755f657f/mmh3-5.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9aeaf53eaa075dd63e81512522fd180097312fb2c9f476333309184285c49ce0", size = 40458, upload-time = "2026-03-05T15:54:01.548Z" }, + { 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/f6/09/a806334ce1d3d50bf782b95fcee8b3648e1e170327d4bb7b4bad2ad7d956/mmh3-5.2.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080c0637aea036f35507e803a4778f119a9b436617694ae1c5c366805f1e997", size = 97242, upload-time = "2026-03-05T15:54:04.536Z" }, + { 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/36/b5/613772c1c6ed5f7b63df55eb131e887cc43720fec392777b95a79d34e640/mmh3-5.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:915e7a2418f10bd1151b1953df06d896db9783c9cfdb9a8ee1f9b3a4331ab503", size = 98524, upload-time = "2026-03-05T15:54:13.122Z" }, + { 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/f1/b9/5e4cca8dcccf298add0a27f3c357bc8cf8baf821d35cdc6165e4bd5a48b0/mmh3-5.2.1-cp311-cp311-win32.whl", hash = "sha256:baeb47635cb33375dee4924cd93d7f5dcaa786c740b08423b0209b824a1ee728", size = 40751, upload-time = "2026-03-05T15:54:18.714Z" }, + { url = "https://files.pythonhosted.org/packages/72/fc/5b11d49247f499bcda591171e9cf3b6ee422b19e70aa2cef2e0ae65ca3b9/mmh3-5.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:1e4ecee40ba19e6975e1120829796770325841c2f153c0e9aecca927194c6a2a", size = 41517, upload-time = "2026-03-05T15:54:19.764Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5f/2a511ee8a1c2a527c77726d5231685b72312c5a1a1b7639ad66a9652aa84/mmh3-5.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:c302245fd6c33d96bd169c7ccf2513c20f4c1e417c07ce9dce107c8bc3f8411f", size = 39287, upload-time = "2026-03-05T15:54:20.904Z" }, + { 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/f6/80/64a02cc3e95c3af0aaa2590849d9ed24a9f14bb93537addde688e039b7c3/mmh3-5.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4eda76074cfca2787c8cf1bec603eaebdddd8b061ad5502f85cddae998d54f00", size = 40500, upload-time = "2026-03-05T15:54:22.953Z" }, + { 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/59/c2/bf4537a8e58e21886ef16477041238cab5095c836496e19fafc34b7445d2/mmh3-5.2.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d0b7e803191db5f714d264044e06189c8ccd3219e936cc184f07106bd17fd7b", size = 97292, upload-time = "2026-03-05T15:54:25.335Z" }, + { 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/dd/38/99f7f75cd27d10d8b899a1caafb9d531f3903e4d54d572220e3d8ac35e89/mmh3-5.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62815d2c67f2dd1be76a253d88af4e1da19aeaa1820146dec52cf8bee2958b16", size = 98623, upload-time = "2026-03-05T15:54:33.801Z" }, + { 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/df/94/6ebb9094cfc7ac5e7950776b9d13a66bb4a34f83814f32ba2abc9494fc68/mmh3-5.2.1-cp312-cp312-win32.whl", hash = "sha256:7374d6e3ef72afe49697ecd683f3da12f4fc06af2d75433d0580c6746d2fa025", size = 40773, upload-time = "2026-03-05T15:54:40.077Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/cd3527198cf159495966551c84a5f36805a10ac17b294f41f67b83f6a4d6/mmh3-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9fed49c6ce4ed7e73f13182760c65c816da006debe67f37635580dfb0fae00", size = 41560, upload-time = "2026-03-05T15:54:41.148Z" }, + { url = "https://files.pythonhosted.org/packages/15/96/6fe5ebd0f970a076e3ed5512871ce7569447b962e96c125528a2f9724470/mmh3-5.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfcb95d9a744e6e2827dfc66ad10e1020e0cac255eb7f85652832d5a264c2fc", size = 39313, upload-time = "2026-03-05T15:54:42.171Z" }, + { url = "https://files.pythonhosted.org/packages/25/a5/9daa0508a1569a54130f6198d5462a92deda870043624aa3ea72721aa765/mmh3-5.2.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:723b2681ed4cc07d3401bbea9c201ad4f2a4ca6ba8cddaff6789f715dd2b391e", size = 40832, upload-time = "2026-03-05T15:54:43.212Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6b/3230c6d80c1f4b766dedf280a92c2241e99f87c1504ff74205ec8cebe451/mmh3-5.2.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:3619473a0e0d329fd4aec8075628f8f616be2da41605300696206d6f36920c3d", size = 41964, upload-time = "2026-03-05T15:54:44.204Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/2f/ed/6f88dda0df67de1612f2e130ffea34cf84aaee5bff5b0aff4dbff2babe34/mmh3-5.2.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:17fbb47f0885ace8327ce1235d0416dc86a211dcd8cc1e703f41523be32cfec8", size = 40330, upload-time = "2026-03-05T15:54:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/3d/66/7516d23f53cdf90f43fce24ab80c28f45e6851d78b46bef8c02084edf583/mmh3-5.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d51fde50a77f81330523562e3c2734ffdca9c4c9e9d355478117905e1cfe16c6", size = 56078, upload-time = "2026-03-05T15:54:48.9Z" }, + { url = "https://files.pythonhosted.org/packages/bc/34/4d152fdf4a91a132cb226b671f11c6b796eada9ab78080fb5ce1e95adaab/mmh3-5.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:19bbd3b841174ae6ed588536ab5e1b1fe83d046e668602c20266547298d939a9", size = 40498, upload-time = "2026-03-05T15:54:49.942Z" }, + { 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/8b/f2/966ea560e32578d453c9e9db53d602cbb1d0da27317e232afa7c38ceba11/mmh3-5.2.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fd96476f04db5ceba1cfa0f21228f67c1f7402296f0e73fee3513aa680ad237b", size = 97320, upload-time = "2026-03-05T15:54:52.072Z" }, + { 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/54/f7/6b16eb1b40ee89bb740698735574536bc20d6cdafc65ae702ea235578e05/mmh3-5.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e4e1f580033335c6f76d1e0d6b56baf009d1a64d6a4816347e4271ba951f46d", size = 98686, upload-time = "2026-03-05T15:55:00.078Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/4b/f9/dc3787ee5c813cc27fe79f45ad4500d9b5437f23a7402435cc34e07c7718/mmh3-5.2.1-cp313-cp313-win32.whl", hash = "sha256:54b64fb2433bc71488e7a449603bf8bd31fbcf9cb56fbe1eb6d459e90b86c37b", size = 40769, upload-time = "2026-03-05T15:55:05.277Z" }, + { url = "https://files.pythonhosted.org/packages/43/67/850e0b5a1e97799822ebfc4ca0e8c6ece3ed8baf7dcdf64de817dfdda2ca/mmh3-5.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:cae6383181f1e345317742d2ddd88f9e7d2682fa4c9432e3a74e47d92dce0229", size = 41563, upload-time = "2026-03-05T15:55:06.283Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/98c90b28e1da5458e19fbfaf4adb5289208d3bfccd45dd14eab216a2f0bb/mmh3-5.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:022aa1a528604e6c83d0a7705fdef0b5355d897a9e0fa3a8d26709ceaa06965d", size = 39310, upload-time = "2026-03-05T15:55:07.323Z" }, + { url = "https://files.pythonhosted.org/packages/63/b4/65bc1fb2bb7f83e91c30865023b1847cf89a5f237165575e8c83aa536584/mmh3-5.2.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:d771f085fcdf4035786adfb1d8db026df1eb4b41dac1c3d070d1e49512843227", size = 40794, upload-time = "2026-03-05T15:55:09.773Z" }, + { url = "https://files.pythonhosted.org/packages/c4/86/7168b3d83be8eb553897b1fac9da8bbb06568e5cfe555ffc329ebb46f59d/mmh3-5.2.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:7f196cd7910d71e9d9860da0ff7a77f64d22c1ad931f1dd18559a06e03109fc0", size = 41923, upload-time = "2026-03-05T15:55:10.924Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9b/b653ab611c9060ce8ff0ba25c0226757755725e789292f3ca138a58082cd/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b1f12bd684887a0a5d55e6363ca87056f361e45451105012d329b86ec19dbe0b", size = 39131, upload-time = "2026-03-05T15:55:11.961Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b4/5a2e0d34ab4d33543f01121e832395ea510132ea8e52cdf63926d9d81754/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d106493a60dcb4aef35a0fac85105e150a11cf8bc2b0d388f5a33272d756c966", size = 39825, upload-time = "2026-03-05T15:55:13.013Z" }, + { url = "https://files.pythonhosted.org/packages/bd/69/81699a8f39a3f8d368bec6443435c0c392df0d200ad915bf0d222b588e03/mmh3-5.2.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44983e45310ee5b9f73397350251cdf6e63a466406a105f1d16cb5baa659270b", size = 40344, upload-time = "2026-03-05T15:55:14.026Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b3/71c8c775807606e8fd8acc5c69016e1caf3200d50b50b6dd4b40ce10b76c/mmh3-5.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:368625fb01666655985391dbad3860dc0ba7c0d6b9125819f3121ee7292b4ac8", size = 56291, upload-time = "2026-03-05T15:55:15.137Z" }, + { url = "https://files.pythonhosted.org/packages/6f/75/2c24517d4b2ce9e4917362d24f274d3d541346af764430249ddcc4cb3a08/mmh3-5.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:72d1cc63bcc91e14933f77d51b3df899d6a07d184ec515ea7f56bff659e124d7", size = 40575, upload-time = "2026-03-05T15:55:16.518Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/e4a360164365ac9f07a25f0f7928e3a66eb9ecc989384060747aa170e6aa/mmh3-5.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e8b4b5580280b9265af3e0409974fb79c64cf7523632d03fbf11df18f8b0181e", size = 40052, upload-time = "2026-03-05T15:55:17.735Z" }, + { url = "https://files.pythonhosted.org/packages/97/ca/120d92223a7546131bbbc31c9174168ee7a73b1366f5463ffe69d9e691fe/mmh3-5.2.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4cbbde66f1183db040daede83dd86c06d663c5bb2af6de1142b7c8c37923dd74", size = 97311, upload-time = "2026-03-05T15:55:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/b6/71/c1a60c1652b8813ef9de6d289784847355417ee0f2980bca002fe87f4ae5/mmh3-5.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8ff038d52ef6aa0f309feeba00c5095c9118d0abf787e8e8454d6048db2037fc", size = 103279, upload-time = "2026-03-05T15:55:20.448Z" }, + { url = "https://files.pythonhosted.org/packages/48/29/ad97f4be1509cdcb28ae32c15593ce7c415db47ace37f8fad35b493faa9a/mmh3-5.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4130d0b9ce5fad6af07421b1aecc7e079519f70d6c05729ab871794eded8617", size = 106290, upload-time = "2026-03-05T15:55:21.6Z" }, + { url = "https://files.pythonhosted.org/packages/77/29/1f86d22e281bd8827ba373600a4a8b0c0eae5ca6aa55b9a8c26d2a34decc/mmh3-5.2.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e0bfe77d238308839699944164b96a2eeccaf55f2af400f54dc20669d8d5f2", size = 113116, upload-time = "2026-03-05T15:55:22.826Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7c/339971ea7ed4c12d98f421f13db3ea576a9114082ccb59d2d1a0f00ccac1/mmh3-5.2.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f963eafc0a77a6c0562397da004f5876a9bcf7265a7bcc3205e29636bc4a1312", size = 120740, upload-time = "2026-03-05T15:55:24.3Z" }, + { url = "https://files.pythonhosted.org/packages/e4/92/3c7c4bdb8e926bb3c972d1e2907d77960c1c4b250b41e8366cf20c6e4373/mmh3-5.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92883836caf50d5255be03d988d75bc93e3f86ba247b7ca137347c323f731deb", size = 99143, upload-time = "2026-03-05T15:55:25.456Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/33dd8706e732458c8375eae63c981292de07a406bad4ec03e5269654aa2c/mmh3-5.2.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57b52603e89355ff318025dd55158f6e71396c0f1f609d548e9ea9c94cc6ce0a", size = 98703, upload-time = "2026-03-05T15:55:26.723Z" }, + { url = "https://files.pythonhosted.org/packages/51/04/76bbce05df76cbc3d396f13b2ea5b1578ef02b6a5187e132c6c33f99d596/mmh3-5.2.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f40a95186a72fa0b67d15fef0f157bfcda00b4f59c8a07cbe5530d41ac35d105", size = 106484, upload-time = "2026-03-05T15:55:28.214Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8f/c6e204a2c70b719c1f62ffd9da27aef2dddcba875ea9c31ca0e87b975a46/mmh3-5.2.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:58370d05d033ee97224c81263af123dea3d931025030fd34b61227a768a8858a", size = 110012, upload-time = "2026-03-05T15:55:29.532Z" }, + { url = "https://files.pythonhosted.org/packages/e3/37/7181efd8e39db386c1ebc3e6b7d1f702a09d7c1197a6f2742ed6b5c16597/mmh3-5.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7be6dfb49e48fd0a7d91ff758a2b51336f1cd21f9d44b20f6801f072bd080cdd", size = 97508, upload-time = "2026-03-05T15:55:31.01Z" }, + { url = "https://files.pythonhosted.org/packages/42/0f/afa7ca2615fd85e1469474bb860e381443d0b868c083b62b41cb1d7ca32f/mmh3-5.2.1-cp314-cp314-win32.whl", hash = "sha256:54fe8518abe06a4c3852754bfd498b30cc58e667f376c513eac89a244ce781a4", size = 41387, upload-time = "2026-03-05T15:55:32.403Z" }, + { url = "https://files.pythonhosted.org/packages/71/0d/46d42a260ee1357db3d486e6c7a692e303c017968e14865e00efa10d09fc/mmh3-5.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3f796b535008708846044c43302719c6956f39ca2d93f2edda5319e79a29efbb", size = 42101, upload-time = "2026-03-05T15:55:33.646Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7b/848a8378059d96501a41159fca90d6a99e89736b0afbe8e8edffeac8c74b/mmh3-5.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:cd471ede0d802dd936b6fab28188302b2d497f68436025857ca72cd3810423fe", size = 39836, upload-time = "2026-03-05T15:55:35.026Z" }, + { url = "https://files.pythonhosted.org/packages/27/61/1dabea76c011ba8547c25d30c91c0ec22544487a8750997a27a0c9e1180b/mmh3-5.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5174a697ce042fa77c407e05efe41e03aa56dae9ec67388055820fb48cf4c3ba", size = 57727, upload-time = "2026-03-05T15:55:36.162Z" }, + { url = "https://files.pythonhosted.org/packages/b7/32/731185950d1cf2d5e28979cc8593016ba1619a295faba10dda664a4931b5/mmh3-5.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0a3984146e414684a6be2862d84fcb1035f4984851cb81b26d933bab6119bf00", size = 41308, upload-time = "2026-03-05T15:55:37.254Z" }, + { url = "https://files.pythonhosted.org/packages/76/aa/66c76801c24b8c9418b4edde9b5e57c75e72c94e29c48f707e3962534f18/mmh3-5.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bd6e7d363aa93bd3421b30b6af97064daf47bc96005bddba67c5ffbc6df426b8", size = 40758, upload-time = "2026-03-05T15:55:38.61Z" }, + { url = "https://files.pythonhosted.org/packages/9e/bb/79a1f638a02f0ae389f706d13891e2fbf7d8c0a22ecde67ba828951bb60a/mmh3-5.2.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:113f78e7463a36dbbcea05bfe688efd7fa759d0f0c56e73c974d60dcfec3dfcc", size = 109670, upload-time = "2026-03-05T15:55:40.13Z" }, + { url = "https://files.pythonhosted.org/packages/26/94/8cd0e187a288985bcfc79bf5144d1d712df9dee74365f59d26e3a1865be6/mmh3-5.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e8ec5f606e0809426d2440e0683509fb605a8820a21ebd120dcdba61b74ef7f", size = 117399, upload-time = "2026-03-05T15:55:42.076Z" }, + { url = "https://files.pythonhosted.org/packages/42/94/dfea6059bd5c5beda565f58a4096e43f4858fb6d2862806b8bbd12cbb284/mmh3-5.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22b0f9971ec4e07e8223f2beebe96a6cfc779d940b6f27d26604040dd74d3a44", size = 120386, upload-time = "2026-03-05T15:55:43.481Z" }, + { url = "https://files.pythonhosted.org/packages/47/cb/f9c45e62aaa67220179f487772461d891bb582bb2f9783c944832c60efd9/mmh3-5.2.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85ffc9920ffc39c5eee1e3ac9100c913a0973996fbad5111f939bbda49204bb7", size = 125924, upload-time = "2026-03-05T15:55:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/a5/83/fe54a4a7c11bc9f623dfc1707decd034245602b076dfc1dcc771a4163170/mmh3-5.2.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aec798c2b01aaa65a55f1124f3405804184373abb318a3091325aece235f67c", size = 135280, upload-time = "2026-03-05T15:55:45.866Z" }, + { url = "https://files.pythonhosted.org/packages/97/67/fe7e9e9c143daddd210cd22aef89cbc425d58ecf238d2b7d9eb0da974105/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55dbbd8ffbc40d1697d5e2d0375b08599dae8746b0b08dea05eee4ce81648fac", size = 110050, upload-time = "2026-03-05T15:55:47.074Z" }, + { url = "https://files.pythonhosted.org/packages/43/c4/6d4b09fcbef80794de447c9378e39eefc047156b290fa3dd2d5257ca8227/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6c85c38a279ca9295a69b9b088a2e48aa49737bb1b34e6a9dc6297c110e8d912", size = 111158, upload-time = "2026-03-05T15:55:48.239Z" }, + { url = "https://files.pythonhosted.org/packages/81/a6/ca51c864bdb30524beb055a6d8826db3906af0834ec8c41d097a6e8573d5/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6290289fa5fb4c70fd7f72016e03633d60388185483ff3b162912c81205ae2cf", size = 116890, upload-time = "2026-03-05T15:55:49.405Z" }, + { url = "https://files.pythonhosted.org/packages/cc/04/5a1fe2e2ad843d03e89af25238cbc4f6840a8bb6c4329a98ab694c71deda/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4fc6cd65dc4d2fdb2625e288939a3566e36127a84811a4913f02f3d5931da52d", size = 123121, upload-time = "2026-03-05T15:55:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/af/4d/3c820c6f4897afd25905270a9f2330a23f77a207ea7356f7aadace7273c0/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:623f938f6a039536cc02b7582a07a080f13fdfd48f87e63201d92d7e34d09a18", size = 110187, upload-time = "2026-03-05T15:55:52.143Z" }, + { url = "https://files.pythonhosted.org/packages/21/54/1d71cd143752361c0aebef16ad3f55926a6faf7b112d355745c1f8a25f7f/mmh3-5.2.1-cp314-cp314t-win32.whl", hash = "sha256:29bc3973676ae334412efdd367fcd11d036b7be3efc1ce2407ef8676dabfeb82", size = 41934, upload-time = "2026-03-05T15:55:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e4/63a2a88f31d93dea03947cccc2a076946857e799ea4f7acdecbf43b324aa/mmh3-5.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:28cfab66577000b9505a0d068c731aee7ca85cd26d4d63881fab17857e0fe1fb", size = 43036, upload-time = "2026-03-05T15:55:55.252Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391, upload-time = "2026-03-05T15:55:56.697Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +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/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { 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/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { 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/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { 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/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { 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/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { 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/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { 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/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { 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/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { 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/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { 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/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { 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/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { 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/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { 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/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { 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/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { 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/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { 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/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { 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" }, +] + +[[package]] +name = "mypy" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size = 14691685, upload-time = "2026-05-11T18:33:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size = 13555165, upload-time = "2026-05-11T18:32:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size = 13994376, upload-time = "2026-05-11T18:32:39.256Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size = 14864618, upload-time = "2026-05-11T18:34:49.765Z" }, + { url = "https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size = 15102063, upload-time = "2026-05-11T18:34:05.855Z" }, + { url = "https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size = 11060564, upload-time = "2026-05-11T18:35:36.494Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size = 9966983, upload-time = "2026-05-11T18:37:14.139Z" }, + { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" }, + { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" }, + { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" }, + { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" }, + { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" }, + { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, + { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, + { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, + { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "myst-parser" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/dc/603751677fff302f34396e206b610f556a59d7fe58b9a2145f54e96b48e8/myst_parser-5.1.0.tar.gz", hash = "sha256:ab69322dc6719dcc7f296479dbb70181b66df6ed315064f92dbc85c0e1bf2f02", size = 101182, upload-time = "2026-05-13T09:38:19.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/dc/f3dfb7488b770f3f67e6545085bf2abea5172e88f57b8ad25ef860ca704c/myst_parser-5.1.0-py3-none-any.whl", hash = "sha256:9c91c52b3cdb4d94a6506e4fab4e2f296c7623a0da0dcbe6de1565c3dad67a8a", size = 85817, upload-time = "2026-05-13T09:38:17.904Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, + { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, + { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, + { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, + { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, + { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, + { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, + { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, + { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, + { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, + { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, + { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, + { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, + { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, + { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, + { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, + { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, +] + +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + +[[package]] +name = "onnxruntime" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flatbuffers" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, + { name = "protobuf" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/e4/5353d7e09ced4a8f473f843223fc75d726b2b5519dcefc12f22a6c92852d/onnxruntime-1.27.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:8ba14a38c570087f3cdb8cfba33f7a38a1e826c1e5b29e17c28ceda0cc910016", size = 18416484, upload-time = "2026-06-15T22:43:43.894Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1f/a2117aa3f144fce88774efa37440d0ca72d0c9144854dfc0961f2b04c6fc/onnxruntime-1.27.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2eb083321af8a236a84c7c140a7f4cecbfa2a987a18c07c78db471c20cd390ef", size = 16419330, upload-time = "2026-06-15T22:42:37.58Z" }, + { url = "https://files.pythonhosted.org/packages/e0/cd/74bb804170ceb622fda9111df31a07b3024f7491472256d3a90b5391a4d2/onnxruntime-1.27.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4f7b0e90d2d212e2c2deaa6c8291616183ab815d3ec558ea12d3ac8b26d36f4", size = 18636930, upload-time = "2026-06-15T22:43:01.584Z" }, + { url = "https://files.pythonhosted.org/packages/fe/8f/5b8e2b85e81735696887175dbaf6409f215683f5ca9d4928fbb038211d32/onnxruntime-1.27.0-cp311-cp311-win_amd64.whl", hash = "sha256:ff050e4f6bf7f12918fa14dcb047c0b02e295f35e86d42532552be4b3d54e977", size = 13356110, upload-time = "2026-06-15T22:43:32.172Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3a/4f568de678126b6a371a93862f015a82138359decd97fcac61fc84b5b774/onnxruntime-1.27.0-cp311-cp311-win_arm64.whl", hash = "sha256:75fbc1e1fb43a39a856c8209c544cca7817b5de7ac16b15b1bdf55d1cc67b9df", size = 13098635, upload-time = "2026-06-15T22:43:19.607Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b7/dd3a524ed93a820dff1af902d0412957ab12499953333e9daa01af5bc480/onnxruntime-1.27.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a14c2ce45312def86b77aea651f46565e45960cf5f0721bfdff449165086ab76", size = 18433506, upload-time = "2026-06-15T22:43:47.026Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/c3b6b17745a1997d784dadc9bd88d713d2e6721139a5a0e885b28cfb79b1/onnxruntime-1.27.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6fddce0539a4898c7bef35b052ffd37935b2190e35488eab99ce91887743ea1", size = 16438140, upload-time = "2026-06-15T22:42:40.666Z" }, + { url = "https://files.pythonhosted.org/packages/26/81/24dd9b31b0fb912ee19ca53ac1c9764bfd79d58a2ccef564eb693be831a5/onnxruntime-1.27.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c65a7438632d55dfbc8a02ee60bd6cf7dd9d1ba05a43d4b851452f32338e194", size = 18658316, upload-time = "2026-06-15T22:43:04.012Z" }, + { url = "https://files.pythonhosted.org/packages/4f/88/8ec9db1a4d126bb8b758992beb40d1249df171917d75f44a327eb5f20dda/onnxruntime-1.27.0-cp312-cp312-win_amd64.whl", hash = "sha256:20c321cf187ba496e648acf6b4cf90b4d398b0d17c2a77fdaeba365b908cc1c1", size = 13358769, upload-time = "2026-06-15T22:43:34.581Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9f/fdad359dfcba7e7cd8815569b304a596531d4efa77a75d77f8b4981891a2/onnxruntime-1.27.0-cp312-cp312-win_arm64.whl", hash = "sha256:d0d1f68868e2ef30ef70998ba9bbbc5c305e9b17041e3936751c1b8aa6aade06", size = 13104440, upload-time = "2026-06-15T22:43:22.893Z" }, + { url = "https://files.pythonhosted.org/packages/fb/2b/54208fd03ad410480bc17edf4869376362da8bbf46fe186ddf4cb5cc20fe/onnxruntime-1.27.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:b3e5b58b8c89c2b20e086e890aa9527377e5c240dc3ecc1640d18e07705eeb1c", size = 18432958, upload-time = "2026-06-15T22:42:53.105Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/24fc51fcbb126da6d032372314e47b55c3faad58f2aa78c0e199ccd20b9c/onnxruntime-1.27.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48b3d87eb560ff6a772240506f3c78d6d27c63cafedd5c775672e1194f968cfd", size = 16438180, upload-time = "2026-06-15T22:42:43.093Z" }, + { url = "https://files.pythonhosted.org/packages/cb/19/14929c3c2fe0b79b41cce24463062bf3afa4cdd3c19dccf00319caa92bff/onnxruntime-1.27.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6872443f236a554921cda6f318c900e2d0c226792cf3534d00e5057c6926e5d2", size = 18658445, upload-time = "2026-06-15T22:43:08.053Z" }, + { url = "https://files.pythonhosted.org/packages/7f/76/59ed932b0244acd7bbbd6449480053a6d958ea66357f022f932872e19287/onnxruntime-1.27.0-cp313-cp313-win_amd64.whl", hash = "sha256:760021bca514d64a811837820d351a08a41741f16f8b4c26450da708fecf14e6", size = 13357856, upload-time = "2026-06-15T22:43:37.315Z" }, + { url = "https://files.pythonhosted.org/packages/79/51/d1ec60ec7b1e2ae2d7340ba52b8a13529140039cd4407ba8dddbbc046582/onnxruntime-1.27.0-cp313-cp313-win_arm64.whl", hash = "sha256:2fdfa9df40a0ded0028ce6f9cd863264237f3970559dea2b81456e9ac4622b94", size = 13104412, upload-time = "2026-06-15T22:43:27.457Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7d/e6bb1c6445c94f708c38cd8fbb7bf0264108c33498b9445c93e60fe6d329/onnxruntime-1.27.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54c0c4e9202c36c4ecdb1f3443f5dfbfd5ee3b54d1362c4b4c6134110e74fb32", size = 16443331, upload-time = "2026-06-15T22:42:45.649Z" }, + { url = "https://files.pythonhosted.org/packages/72/1b/b18b31e806eabc41077810199fbbb36fbc2d5f19912416e5ccfbf73053d1/onnxruntime-1.27.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b215aa662c8f983f7d6dedafe65a9be72c26e5338e0fe98b3e0422c32c85428", size = 18670967, upload-time = "2026-06-15T22:43:10.621Z" }, + { url = "https://files.pythonhosted.org/packages/3a/37/48ab79c39b58a7c9f6f5aac1fa0ff2b993eb2643393d6ed9e839ddb6f347/onnxruntime-1.27.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0874edc171f470fc4dd2bbb60bc0989612ed1a8b89b365cda016630a93227f13", size = 18433941, upload-time = "2026-06-15T22:42:58.867Z" }, + { url = "https://files.pythonhosted.org/packages/6e/24/d535ca8a09dbf697f853377c8dc0820dbcaae5f334316b400b953afbcba8/onnxruntime-1.27.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b51c014cf1a4fcd93c29a97eac8071fa27710dae05a4d0380bb60a66d60a62c", size = 16439970, upload-time = "2026-06-15T22:42:48.023Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b1/ea9ee80c0bdaa4efb13f29f8c236f3740f6655e8c092a2d119515a5a652c/onnxruntime-1.27.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:445fb702ea5241ba813a3ce2febe2e9408a64f6ad2eb610924322c536165f7cd", size = 18659240, upload-time = "2026-06-15T22:43:13.165Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f2/1404507d76a21940e8bf46f414e3d1abd94dc888cb89a30f4a540275846f/onnxruntime-1.27.0-cp314-cp314-win_amd64.whl", hash = "sha256:49e416be0d717338b6d041b99911b716d70c397d277056450724f93bdded3fc2", size = 13685306, upload-time = "2026-06-15T22:43:40.416Z" }, + { url = "https://files.pythonhosted.org/packages/10/e5/ca5cf012ccccb806c70e94aadfebca5606acc62b33eb88cec13352d0778f/onnxruntime-1.27.0-cp314-cp314-win_arm64.whl", hash = "sha256:856032937dd3bc7a7c141909c8d7ae4fde3e3f59bddf061ae627b9a051bda95c", size = 13456280, upload-time = "2026-06-15T22:43:29.693Z" }, + { url = "https://files.pythonhosted.org/packages/67/7b/dca330a8397e9d816c976d7aed4e24a4a2d279bb1e551e3d0221d1389b1d/onnxruntime-1.27.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6197a02e3f620c4dc13cff51b80672409fc1ffab3aa2593911b19fd322ff48b", size = 16443274, upload-time = "2026-06-15T22:42:50.467Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f6/2bac21f722aa45d876d4a51f26bd0ef30e704068a3cd5021a5a7cd784271/onnxruntime-1.27.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:370d211e1ceeac4cd5f45301655463ac59e27cdc74d9f7aeb2d19ff4b7a76715", size = 18670781, upload-time = "2026-06-15T22:43:17.151Z" }, +] + +[[package]] +name = "openai" +version = "2.44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/f5/7c7cb955305cb41f7f3c5fd7e0e38bf6bbf2658468863d4b7b868a5cb8df/openai-2.44.0.tar.gz", hash = "sha256:68a5a5ffad82b8ff7d451c437529fb64f7c3b8123aaf0c021966a882d9e3947d", size = 988753, upload-time = "2026-06-24T20:56:02.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/f4/561ed79fd94876160018a5e75254cfcb9b0e62d4dded9dcb20072e86d623/openai-2.44.0-py3-none-any.whl", hash = "sha256:0a2a3ab2e29aeda368700f662ff9ba0f9df17ba4c54577a64e08b8115a3cc0ad", size = 1366216, upload-time = "2026-06-24T20:55:58.882Z" }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/cc/e4c9584181f86494df0f6bdec1a4f3280c50db44704dc2a407e994fc87bb/opentelemetry_api-1.43.0.tar.gz", hash = "sha256:107d0d03857ea8fc7c5fcbbbd83f800c281f0d560553d61c1d675fccfd1761c1", size = 73476, upload-time = "2026-06-24T15:19:55.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/83/6dba32b85f31868400440dc7ad2ca1eab94cbbf3a7b0459ed39f8311a9e2/opentelemetry_api-1.43.0-py3-none-any.whl", hash = "sha256:20acf45e9b21851926835292e4045d290acade1edd2ff3de86d2f069687ba1fd", size = 61912, upload-time = "2026-06-24T15:19:35.434Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/c1/e8098490ab15abf116dcaf9fa89ededcb35547c7d08d4b5a62f573dc1e63/opentelemetry_exporter_otlp_proto_common-1.43.0.tar.gz", hash = "sha256:c4e32ba6d6b13bdb2b8f6764c4fd28d00192826561aa04f6d14eedfce7ac076f", size = 20197, upload-time = "2026-06-24T15:20:00.247Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/b2/41ebc74ae1d5859901f1b69305de58724bf043381103d6ef413521cbc35a/opentelemetry_exporter_otlp_proto_common-1.43.0-py3-none-any.whl", hash = "sha256:123c3f9cc87218562490c63b36f497bf3a722faf174a515d1443f31ababa6264", size = 17048, upload-time = "2026-06-24T15:19:41.264Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.43.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/e2/1d/6336453716ca0a240d4417d19e6d5b77a5e7163e5670ec4f7ec4d3ede7bf/opentelemetry_exporter_otlp_proto_grpc-1.43.0.tar.gz", hash = "sha256:1b3e0627daa9bc21884d4a13946807c255eb558bfe5bdd543dffb6f4c9faee0d", size = 27213, upload-time = "2026-06-24T15:20:00.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/74/2700b5d5c946bf2dba87073fce3dfc198c46bc92ea3d5693f54bc51c90b1/opentelemetry_exporter_otlp_proto_grpc-1.43.0-py3-none-any.whl", hash = "sha256:6a10d1feacffffda19acacbf277b736094b1e2f4dbb98c90ccb2c6e1962e2ec6", size = 19626, upload-time = "2026-06-24T15:19:42.233Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.43.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/fc/92/0b9f56412483a8891d4843890294796c9df8ab42417bd9bad8035d840cb3/opentelemetry_exporter_otlp_proto_http-1.43.0.tar.gz", hash = "sha256:fa8a42bb7d00ee5391f4c0b04d8e6a46c03caa437903296ab73a81dc11ba118f", size = 25406, upload-time = "2026-06-24T15:20:01.515Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/20/b685ed7af2e17c29ffc8af56f1fa8bc2033258fc30fb0d2b722f49d13ba0/opentelemetry_exporter_otlp_proto_http-1.43.0-py3-none-any.whl", hash = "sha256:647f603aa8efdbdb4dbff842e0729d0406a6fff26b295a72d3d60e7d963b2610", size = 21795, upload-time = "2026-06-24T15:19:43.164Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.64b0" +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/7e/97/02fe6e1c8b1ffac42d0b429c18080edb24e0e0d18c86612edf72b5752382/opentelemetry_instrumentation-0.64b0.tar.gz", hash = "sha256:b47d528dead6271d7743114417eb67fc915bd9258111c48dbf9a4951d2efa88d", size = 41935, upload-time = "2026-06-24T15:19:12.951Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/0c/cb9fe342de5299c7af24582eb7d788661cc53a1c4b904da92309caaa9417/opentelemetry_instrumentation-0.64b0-py3-none-any.whl", hash = "sha256:133ab7ffca796557aec059bf6be3190a34b6dea987f25be3d9409e230cbdad8b", size = 35880, upload-time = "2026-06-24T15:18:17.277Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-threading" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/ef/d4a16eb4a0f0971e351ec2ff95846b501b66a619d07d90822942d70a3adc/opentelemetry_instrumentation_threading-0.64b0.tar.gz", hash = "sha256:0a07d7329f69dfae5036a7cb184f502c8b91cb0538012f5304bd32ffe9ade451", size = 9080, upload-time = "2026-06-24T15:19:43.22Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/b3/8838d5f3db35b3854516ccfbf8ec26138d40388d828407182c890c3aa9d0/opentelemetry_instrumentation_threading-0.64b0-py3-none-any.whl", hash = "sha256:a285ffa750a958f7d368e947f5679a0214d588242cbffba0f5934cb02e9a17f4", size = 8487, upload-time = "2026-06-24T15:19:00.813Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/b9/d357faefb40bda1d4799913e6af611171ff22a2dedcb93576bc92242d056/opentelemetry_proto-1.43.0.tar.gz", hash = "sha256:224778df17e1f3fafeaaa21d874236ca5f6ffc2f86e0899298ec7351aac27924", size = 46481, upload-time = "2026-06-24T15:20:07.625Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/a7/3e5308cf548b8f72529c7db1afdb3a404211982376a12927fd7759f77bf3/opentelemetry_proto-1.43.0-py3-none-any.whl", hash = "sha256:c58f1f7ef84bc7dc2834016c0c37fe0081dde7ca9f6339be1970fbf9cdaaa90d", size = 72489, upload-time = "2026-06-24T15:19:51.164Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.43.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/3e/eb/5041074274ac0956b03637cc039d434569112468e875eddfcc9a0674ce06/opentelemetry_sdk-1.43.0.tar.gz", hash = "sha256:d8187c81c162df9913e4003dd6485f7390d9a24fc17026ec7387b8b8218b08e9", size = 254744, upload-time = "2026-06-24T15:20:08.467Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e3/b17be23af124201c9f52eececd4cc8ddfed1597d37b4ee771895d325805c/opentelemetry_sdk-1.43.0-py3-none-any.whl", hash = "sha256:d1323a547c1ce69d6a069a17a44b7da82bb8b332051ecb074041f87642c86823", size = 178852, upload-time = "2026-06-24T15:19:52.169Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/30/5f26df29509eccd86b99b481ac9ffa39da49ba9577cc69071c552ae30447/opentelemetry_semantic_conventions-0.64b0.tar.gz", hash = "sha256:72f76fb2d1582d9d033dd1fcd84532e961e6ff3d90d24ba6fabc72975a83864c", size = 148340, upload-time = "2026-06-24T15:20:09.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/ca/23ba87a221b574a7c5a99d48849d80bfe8b047624681357e2b002e566187/opentelemetry_semantic_conventions-0.64b0-py3-none-any.whl", hash = "sha256:ea77e85e354b8f604ddbe5f3d9135216f982fa4d77e5859ac30f6d8a50505aa6", size = 203713, upload-time = "2026-06-24T15:19:53.339Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/51/3fb9e65ae76ee97bd611869a503fa3fc0a6e81dd8b737cf3003f682df7ff/orjson-3.11.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f", size = 228522, upload-time = "2026-05-06T15:09:35.362Z" }, + { url = "https://files.pythonhosted.org/packages/16/fa/9d54b07cb3f3b0bfd57841478e42d7a0ece4a9f49f9907eecf5a45461687/orjson-3.11.9-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c", size = 128463, upload-time = "2026-05-06T15:09:37.063Z" }, + { url = "https://files.pythonhosted.org/packages/88/b1/6ceafc2eefd0a553e3be77ce6c49d107e772485d9568629376171c50e634/orjson-3.11.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5", size = 132306, upload-time = "2026-05-06T15:09:38.299Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/f11311285324a40aab1e3031385c50b635a7cd0734fdaf60c7e89a696f60/orjson-3.11.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c", size = 127988, upload-time = "2026-05-06T15:09:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/9e/85/0ef63bcf1337f44031ce9b91b1919563f62a37527b3ea4368bb15a22e5d7/orjson-3.11.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd", size = 135188, upload-time = "2026-05-06T15:09:40.957Z" }, + { url = "https://files.pythonhosted.org/packages/05/94/b0d27090ea8a2095db3c2bd1b1c96f96f19bbb494d7fef33130e846e613d/orjson-3.11.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62", size = 145937, upload-time = "2026-05-06T15:09:42.249Z" }, + { url = "https://files.pythonhosted.org/packages/09/eb/75d50c29c05b8054013e221e598820a365c8e64065312e75e202ed880709/orjson-3.11.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877", size = 132758, upload-time = "2026-05-06T15:09:43.945Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/360686f39348aa88827cb6fbf7dc606fd41c831a35235e1abf1db8e3a9e6/orjson-3.11.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1", size = 133971, upload-time = "2026-05-06T15:09:45.239Z" }, + { url = "https://files.pythonhosted.org/packages/0e/30/3178eb16f3221aeef068b6f1f1ebe05f656ea5c6dffe9f6c917329fe17a3/orjson-3.11.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd", size = 141685, upload-time = "2026-05-06T15:09:46.858Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f1/ff2f19ed0225f9680fafa42febca3570dd59444ebf190980738d376214c2/orjson-3.11.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff", size = 415167, upload-time = "2026-05-06T15:09:48.312Z" }, + { url = "https://files.pythonhosted.org/packages/9b/61/863bddf0da6e9e586765414debd54b4e58db05f560902b6d00658cb88636/orjson-3.11.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980", size = 147913, upload-time = "2026-05-06T15:09:49.733Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4081492586d75b073d60c5271a8d0f05a0955cabf1e34c8473f6fcd84235/orjson-3.11.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2", size = 136959, upload-time = "2026-05-06T15:09:51.311Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bd/70b6ab193594d7abb875320c0a7c8335e846f28968c432c31042409c3c8d/orjson-3.11.9-cp311-cp311-win32.whl", hash = "sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180", size = 131533, upload-time = "2026-05-06T15:09:52.637Z" }, + { url = "https://files.pythonhosted.org/packages/3f/17/1a1a228183d62d1b77e2c30d210f47dd4768b310ebe1607c63e3c0e3a71e/orjson-3.11.9-cp311-cp311-win_amd64.whl", hash = "sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02", size = 127106, upload-time = "2026-05-06T15:09:54.204Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/285de5fa296d09681ee9c546cd4a8aeb773b701cf343dc125994f4d52953/orjson-3.11.9-cp311-cp311-win_arm64.whl", hash = "sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697", size = 126848, upload-time = "2026-05-06T15:09:55.551Z" }, + { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, + { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, + { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, + { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, + { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, + { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, + { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, + { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, + { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, + { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, + { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, + { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, + { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, + { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, + { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, + { url = "https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, + { url = "https://files.pythonhosted.org/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, + { url = "https://files.pythonhosted.org/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, + { url = "https://files.pythonhosted.org/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, +] + +[[package]] +name = "ormsgpack" +version = "1.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031, upload-time = "2026-01-18T20:55:28.023Z" } +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/78/29/6d5758fabef3babdf4bbbc453738cc7de9cd3334e4c38dd5737e27b85653/ormsgpack-1.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:73670ed0375ecc303858e3613f407628dd1fca18fe6ac57b7b7ce66cc7bb006c", size = 117182, upload-time = "2026-01-18T20:55:31.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/17a15549233c37e7fd054c48fe9207492e06b026dbd872b826a0b5f833b6/ormsgpack-1.12.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2be829954434e33601ae5da328cccce3266b098927ca7a30246a0baec2ce7bd", size = 111464, upload-time = "2026-01-18T20:55:38.811Z" }, + { 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/7a/0c/9803aa883d18c7ef197213cd2cbf73ba76472a11fe100fb7dab2884edf48/ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4", size = 117462, upload-time = "2026-01-18T20:55:47.726Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9e/029e898298b2cc662f10d7a15652a53e3b525b1e7f07e21fef8536a09bb8/ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6", size = 111559, upload-time = "2026-01-18T20:55:54.273Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/7b/e8/0fb45f57a2ada1fed374f7494c8cd55e2f88ccd0ab0a669aa3468716bf5f/ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9", size = 117459, upload-time = "2026-01-18T20:55:56.876Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d4/0cfeea1e960d550a131001a7f38a5132c7ae3ebde4c82af1f364ccc5d904/ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709", size = 111577, upload-time = "2026-01-18T20:55:43.605Z" }, + { url = "https://files.pythonhosted.org/packages/94/16/24d18851334be09c25e87f74307c84950f18c324a4d3c0b41dabdbf19c29/ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c", size = 378717, upload-time = "2026-01-18T20:55:26.164Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a2/88b9b56f83adae8032ac6a6fa7f080c65b3baf9b6b64fd3d37bd202991d4/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553", size = 203183, upload-time = "2026-01-18T20:55:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/a9/80/43e4555963bf602e5bdc79cbc8debd8b6d5456c00d2504df9775e74b450b/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13", size = 210814, upload-time = "2026-01-18T20:55:33.973Z" }, + { url = "https://files.pythonhosted.org/packages/78/e1/7cfbf28de8bca6efe7e525b329c31277d1b64ce08dcba723971c241a9d60/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d", size = 212634, upload-time = "2026-01-18T20:55:28.634Z" }, + { url = "https://files.pythonhosted.org/packages/95/f8/30ae5716e88d792a4e879debee195653c26ddd3964c968594ddef0a3cc7e/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede", size = 387139, upload-time = "2026-01-18T20:56:02.013Z" }, + { url = "https://files.pythonhosted.org/packages/dc/81/aee5b18a3e3a0e52f718b37ab4b8af6fae0d9d6a65103036a90c2a8ffb5d/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e", size = 482578, upload-time = "2026-01-18T20:55:35.117Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/71c9ba472d5d45f7546317f467a5fc941929cd68fb32796ca3d13dcbaec2/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285", size = 425539, upload-time = "2026-01-18T20:56:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a6/ac99cd7fe77e822fed5250ff4b86fa66dd4238937dd178d2299f10b69816/ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f", size = 117493, upload-time = "2026-01-18T20:56:07.343Z" }, + { url = "https://files.pythonhosted.org/packages/3a/67/339872846a1ae4592535385a1c1f93614138566d7af094200c9c3b45d1e5/ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c", size = 111579, upload-time = "2026-01-18T20:55:21.161Z" }, + { url = "https://files.pythonhosted.org/packages/49/c2/6feb972dc87285ad381749d3882d8aecbde9f6ecf908dd717d33d66df095/ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8", size = 378721, upload-time = "2026-01-18T20:55:52.12Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9a/900a6b9b413e0f8a471cf07830f9cf65939af039a362204b36bd5b581d8b/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033", size = 203170, upload-time = "2026-01-18T20:55:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/87/4c/27a95466354606b256f24fad464d7c97ab62bce6cc529dd4673e1179b8fb/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d", size = 212816, upload-time = "2026-01-18T20:55:23.501Z" }, + { url = "https://files.pythonhosted.org/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232, upload-time = "2026-01-18T20:55:45.448Z" }, +] + +[[package]] +name = "overrides" +version = "7.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pdfminer-six" +version = "20260107" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "charset-normalizer" }, + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/a4/5cec1112009f0439a5ca6afa8ace321f0ab2f48da3255b7a1c8953014670/pdfminer_six-20260107.tar.gz", hash = "sha256:96bfd431e3577a55a0efd25676968ca4ce8fd5b53f14565f85716ff363889602", size = 8512094, upload-time = "2026-01-07T13:29:12.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/8b/28c4eaec9d6b036a52cb44720408f26b1a143ca9bce76cc19e8f5de00ab4/pdfminer_six-20260107-py3-none-any.whl", hash = "sha256:366585ba97e80dffa8f00cebe303d2f381884d8637af4ce422f1df3ef38111a9", size = 6592252, upload-time = "2026-01-07T13:29:10.742Z" }, +] + +[[package]] +name = "pdfplumber" +version = "0.11.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pdfminer-six" }, + { name = "pillow" }, + { name = "pypdfium2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/56/6f450312ba05a27d7713b73857c1a25100dbda04fbc1331b13fb227a607d/pdfplumber-0.11.10.tar.gz", hash = "sha256:b95b2d28c66efb0a794a83b88c6c6aea5987532a445d20a1cbcfa657022e6e57", size = 102892, upload-time = "2026-06-15T03:31:31.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/9a/07d658e1e7fad860f1c541ab941348125dbdab773be3a0afaf32361866c7/pdfplumber-0.11.10-py3-none-any.whl", hash = "sha256:7741ea81bf165b474b153e6789d10d18e06b6ddcf3ec84289c3ef2fed6802580", size = 60047, upload-time = "2026-06-15T03:31:29.702Z" }, +] + +[[package]] +name = "pillow" +version = "12.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" }, + { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" }, + { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" }, + { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" }, + { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" }, + { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" }, + { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" }, + { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, + { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, + { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, + { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, + { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, + { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, + { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, + { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, + { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, + { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, + { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, + { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, + { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, + { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" }, + { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" }, + { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" }, + { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "portalocker" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/f8/969e6f280201b40b31bcb62843c619f343dcc351dff83a5891530c9dd60e/portalocker-2.7.0.tar.gz", hash = "sha256:032e81d534a88ec1736d03f780ba073f047a06c478b06e2937486f334e955c51", size = 20183, upload-time = "2023-01-18T23:36:14.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/df/d4f711d168524f5aebd7fb30969eaa31e3048cf8979688cde3b08f6e5eb8/portalocker-2.7.0-py2.py3-none-any.whl", hash = "sha256:a07c5b4f3985c3cf4798369631fb7011adb498e2a46d8440efc75a8f29a0f983", size = 15502, upload-time = "2023-01-18T23:36:12.849Z" }, +] + +[[package]] +name = "posthog" +version = "5.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backoff" }, + { name = "distro" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/20/60ae67bb9d82f00427946218d49e2e7e80fb41c15dc5019482289ec9ce8d/posthog-5.4.0.tar.gz", hash = "sha256:701669261b8d07cdde0276e5bc096b87f9e200e3b9589c5ebff14df658c5893c", size = 88076, upload-time = "2025-06-20T23:19:23.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/98/e480cab9a08d1c09b1c59a93dade92c1bb7544826684ff2acbfd10fcfbd4/posthog-5.4.0-py3-none-any.whl", hash = "sha256:284dfa302f64353484420b52d4ad81ff5c2c2d1d607c4e2db602ac72761831bd", size = 105364, upload-time = "2025-06-20T23:19:22.001Z" }, +] + +[[package]] +name = "propcache" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/f1/8a8cc1c2c7e7934ab77e0163414f736fadbc0f5e8dd9673b952355ac175b/propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78", size = 90744, upload-time = "2026-05-08T20:59:45.799Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f4/651b1225e976bd1a2ba5cfba0c29d096581c2636b437e3a9a7ab6276270a/propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959", size = 52033, upload-time = "2026-05-08T20:59:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7", size = 52754, upload-time = "2026-05-08T20:59:49.202Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fe/b3551b41bbc2f5b5bb088fc6920567cd43101253e68fbaa261339eb96fe1/propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511", size = 57573, upload-time = "2026-05-08T20:59:50.778Z" }, + { url = "https://files.pythonhosted.org/packages/83/27/ab851ebd1b7172e3e161f5f8d39e315d54a91bea246f01f4d872d3376aef/propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660", size = 60645, upload-time = "2026-05-08T20:59:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/466b3d18022e9897cbda9c735c493c5bd747d7a4c6f5ea1480b4cec434b6/propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66", size = 61563, upload-time = "2026-05-08T20:59:53.866Z" }, + { url = "https://files.pythonhosted.org/packages/27/1b/16ab7f2cf2041da2f60d156ba64c2484eadf9168075b4ff43c3ef60045af/propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b", size = 58888, upload-time = "2026-05-08T20:59:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/0a/67/bb777ffd907633563bf35fd859c4ce97b0512c32f4633cf5d1eb7c33512b/propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67", size = 59253, upload-time = "2026-05-08T20:59:57.075Z" }, + { url = "https://files.pythonhosted.org/packages/b9/42/64f8d90b73fd9cdc1499b48057ff6d9cd2a98a25734c9bb62ecf07e87061/propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f", size = 57558, upload-time = "2026-05-08T20:59:58.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/02/dba5bc03c9041f2092ea55a449caf5dfe68352c6654511b29ba0654ddb69/propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c", size = 55007, upload-time = "2026-05-08T20:59:59.837Z" }, + { url = "https://files.pythonhosted.org/packages/14/c0/43f649c7aa2a77a3b100d84e9dea3a483120ecb608bfe36ce49eaff517fe/propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0", size = 60355, upload-time = "2026-05-08T21:00:01.144Z" }, + { url = "https://files.pythonhosted.org/packages/83/c0/435dafd27f1cb4a495381dae60e25883ccfe4020bb72818e8184c1678092/propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6", size = 59057, upload-time = "2026-05-08T21:00:02.401Z" }, + { url = "https://files.pythonhosted.org/packages/53/ae/6e292df9135d659944e96cb3389258e4a663e5b2b5f6c217ef0ddc8d2f73/propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27", size = 61938, upload-time = "2026-05-08T21:00:03.638Z" }, + { url = "https://files.pythonhosted.org/packages/0b/42/314ebc50d8159055411fd6b0bda322ff510e4b1f7d2e4927940ad0f6af20/propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f", size = 59731, upload-time = "2026-05-08T21:00:04.881Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9b/2da6dee38871c3c8772fabc2758325a5c9077d6d18c597737dc04dd884cd/propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0", size = 38966, upload-time = "2026-05-08T21:00:06.511Z" }, + { url = "https://files.pythonhosted.org/packages/42/4e/f17363fb58c0afe05b067361cb6d86ed2d29de6506779a27547c4d183075/propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82", size = 42135, upload-time = "2026-05-08T21:00:08.088Z" }, + { url = "https://files.pythonhosted.org/packages/c6/eb/6af6685077d22e8b33358d3c548e3282706a0b3cd85044ffba4e5dd08e3b/propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab", size = 38381, upload-time = "2026-05-08T21:00:09.692Z" }, + { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, + { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, + { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, + { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, + { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" }, + { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" }, + { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" }, + { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" }, + { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" }, + { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" }, + { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" }, + { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" }, + { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" }, + { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" }, + { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" }, + { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" }, + { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" }, + { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" }, + { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" }, + { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" }, + { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" }, + { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" }, + { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" }, + { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" }, + { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" }, + { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, +] + +[[package]] +name = "protobuf" +version = "7.35.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, +] + +[[package]] +name = "pyarrow" +version = "24.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/13/13e1069b351bdc3881266e11147ffccf687505dbb0ea74036237f5d454a5/pyarrow-24.0.0.tar.gz", hash = "sha256:85fe721a14dd823aca09127acbb06c3ca723efbd436c004f16bca601b04dcc83", size = 1180261, upload-time = "2026-04-21T10:51:25.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/c9/a47ab7ece0d86cbe6678418a0fbd1ac4bb493b9184a3891dfa0e7f287ae0/pyarrow-24.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b0e131f880cda8d04e076cee175a46fc0e8bc8b65c99c6c09dff6669335fde74", size = 35068898, upload-time = "2026-04-21T10:46:36.599Z" }, + { url = "https://files.pythonhosted.org/packages/d1/bc/8db86617a9a58008acf8913d6fed68ea2a46acb6de928db28d724c891a68/pyarrow-24.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:1b2fe7f9a5566401a0ef2571f197eb92358925c1f0c8dba305d6e43ea0871bb3", size = 36679915, upload-time = "2026-04-21T10:46:42.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8e/fb178720400ef69db251eb4a9c3ccf4af269bc1feb5055529b8fc87170d1/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0b3537c00fb8d384f15ac1e79b6eb6db04a16514c8c1d22e59a9b95c8ba42868", size = 45697931, upload-time = "2026-04-21T10:46:48.403Z" }, + { url = "https://files.pythonhosted.org/packages/f3/27/99c42abe8e21b44f4917f62631f3aa31404882a2c41d8a4cd5c110e13d52/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:14e31a3c9e35f1ab6356c6378f6f72830e6d2d5f1791df3774a7b097d18a6a1e", size = 48837449, upload-time = "2026-04-21T10:46:55.329Z" }, + { url = "https://files.pythonhosted.org/packages/36/b6/333749e2666e9032891125bf9c691146e92901bece62030ac1430e2e7c88/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7d9a514e73bc42711e6a35aaccf3587c520024fe0a25d830a1a8a27c15f4f57", size = 49395949, upload-time = "2026-04-21T10:47:01.869Z" }, + { url = "https://files.pythonhosted.org/packages/17/25/c5201706a2dd374e8ba6ee3fd7a8c89fb7ffc16eed5217a91fd2bd7f7626/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b196eb3f931862af3fa84c2a253514d859c08e0d8fe020e07be12e75a5a9780c", size = 51912986, upload-time = "2026-04-21T10:47:09.872Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d2/4d1bbba65320b21a49678d6fbdc6ff7c649251359fdcfc03568c4136231d/pyarrow-24.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:35405aecb474e683fb36af650618fd5340ee5471fc65a21b36076a18bbc6c981", size = 27255371, upload-time = "2026-04-21T10:47:15.943Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a9/9686d9f07837f91f775e8932659192e02c74f9d8920524b480b85212cc68/pyarrow-24.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6233c9ed9ab9d1db47de57d9753256d9dcffbf42db341576099f0fd9f6bf4810", size = 34981559, upload-time = "2026-04-21T10:47:22.17Z" }, + { url = "https://files.pythonhosted.org/packages/80/b6/0ddf0e9b6ead3474ab087ae598c76b031fc45532bf6a63f3a553440fb258/pyarrow-24.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:f7616236ec1bc2b15bfdec22a71ab38851c86f8f05ff64f379e1278cf20c634a", size = 36663654, upload-time = "2026-04-21T10:47:28.315Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3b/926382efe8ce27ba729071d3566ade6dfb86bdf112f366000196b2f5780a/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1617043b99bd33e5318ae18eb2919af09c71322ef1ca46566cdafc6e6712fb66", size = 45679394, upload-time = "2026-04-21T10:47:34.821Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7a/829f7d9dfd37c207206081d6dad474d81dde29952401f07f2ba507814818/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6165461f55ef6314f026de6638d661188e3455d3ec49834556a0ebbdbace18bb", size = 48863122, upload-time = "2026-04-21T10:47:42.056Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e8/f88ce625fe8babaae64e8db2d417c7653adb3019b08aae85c5ed787dc816/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b13dedfe76a0ad2d1d859b0811b53827a4e9d93a0bcb05cf59333ab4980cc7e", size = 49376032, upload-time = "2026-04-21T10:47:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/36/7a/82c363caa145fff88fb475da50d3bf52bb024f61917be5424c3392eaf878/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25ea65d868eb04015cd18e6df2fbe98f07e5bda2abefabcb88fce39a947716f6", size = 51929490, upload-time = "2026-04-21T10:47:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/66/1c/e3e72c8014ad2743ca64a701652c733cc5cbcee15c0463a32a8c55518d9e/pyarrow-24.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:295f0a7f2e242dabd513737cf076007dc5b2d59237e3eca37b05c0c6446f3826", size = 27355660, upload-time = "2026-04-21T10:48:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a1abf004482026ddc17f4503db227787fa3cfe41ec5091ff20e4fea55e57/pyarrow-24.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:02b001b3ed4723caa44f6cd1af2d5c86aa2cf9971dacc2ffa55b21237713dfba", size = 34976759, upload-time = "2026-04-21T10:48:07.258Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4a/34f0a36d28a2dd32225301b79daad44e243dc1a2bb77d43b60749be255c4/pyarrow-24.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:04920d6a71aabd08a0417709efce97d45ea8e6fb733d9ca9ecffb13c67839f68", size = 36658471, upload-time = "2026-04-21T10:48:13.347Z" }, + { url = "https://files.pythonhosted.org/packages/1f/78/543b94712ae8bb1a6023bcc1acf1a740fbff8286747c289cd9468fced2a5/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a964266397740257f16f7bb2e4f08a0c81454004beab8ff59dd531b73610e9f2", size = 45675981, upload-time = "2026-04-21T10:48:20.201Z" }, + { url = "https://files.pythonhosted.org/packages/84/9f/8fb7c222b100d314137fa40ec050de56cd8c6d957d1cfff685ce72f15b17/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f066b179d68c413374294bc1735f68475457c933258df594443bb9d88ddc2a0", size = 48859172, upload-time = "2026-04-21T10:48:27.541Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d3/1ea72538e6c8b3b475ed78d1049a2c518e655761ea50fe1171fc855fcab7/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1183baeb14c5f587b1ec52831e665718ce632caab84b7cd6b85fd44f96114495", size = 49385733, upload-time = "2026-04-21T10:48:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/c3/be/c3d8b06a1ba35f2260f8e1f771abbee7d5e345c0937aab90675706b1690a/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:806f24b4085453c197a5078218d1ee08783ebbba271badd153d1ae22a3ee804f", size = 51934335, upload-time = "2026-04-21T10:48:42.099Z" }, + { url = "https://files.pythonhosted.org/packages/9c/62/89e07a1e7329d2cde3e3c6994ba0839a24977a2beda8be6005ea3d860b99/pyarrow-24.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4505fc6583f7b05ab854934896bcac8253b04ac1171a77dfb73efef92076d91", size = 27271748, upload-time = "2026-04-21T10:49:42.532Z" }, + { url = "https://files.pythonhosted.org/packages/17/1a/cff3a59f80b5b1658549d46611b67163f65e0664431c076ad728bf9d5af4/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1a4e45017efbf115032e4475ee876d525e0e36c742214fbe405332480ecd6275", size = 35238554, upload-time = "2026-04-21T10:48:48.526Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/cce0f42a327bfef2c420fb6078a3eb834826e5d6697bf3009fe11d2ad051/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:7986f1fa71cee060ad00758bcc79d3a93bab8559bf978fab9e53472a2e25a17b", size = 36782301, upload-time = "2026-04-21T10:48:55.181Z" }, + { url = "https://files.pythonhosted.org/packages/2a/66/8e560d5ff6793ca29aca213c53eec0dd482dd46cb93b2819e5aab52e4252/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d3e0b61e8efb24ed38898e5cdc5fffa9124be480008d401a1f8071500494ae42", size = 45721929, upload-time = "2026-04-21T10:49:03.676Z" }, + { url = "https://files.pythonhosted.org/packages/27/0c/a26e25505d030716e078d9f16eb74973cbf0b33b672884e9f9da1c83b871/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:55a3bc1e3df3b5567b7d27ef551b2283f0c68a5e86f1cd56abc569da4f31335b", size = 48825365, upload-time = "2026-04-21T10:49:11.714Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/771f9ecb0c65e73fe9dccdd1717901b9594f08c4515d000c7c62df573811/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:641f795b361874ac9da5294f8f443dfdbee355cf2bd9e3b8d97aaac2306b9b37", size = 49451819, upload-time = "2026-04-21T10:49:21.474Z" }, + { url = "https://files.pythonhosted.org/packages/48/da/61ae89a88732f5a785646f3ec6125dbb640fa98a540eb2b9889caa561403/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8adc8e6ce5fccf5dc707046ae4914fd537def529709cc0d285d37a7f9cd442ca", size = 51909252, upload-time = "2026-04-21T10:49:31.164Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1a/8dd5cafab7b66573fa91c03d06d213356ad4edd71813aa75e08ce2b3a844/pyarrow-24.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9b18371ad2f44044b81a8d23bc2d8a9b6a6226dca775e8e16cfee640473d6c5d", size = 27388127, upload-time = "2026-04-21T10:49:37.334Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/d022a34ff05d2cbedd8ccf841fc1f532ecfa9eb5ed1711b56d0e0ea71fc9/pyarrow-24.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:1cc9057f0319e26333b357e17f3c2c022f1a83739b48a88b25bfd5fa2dc18838", size = 35007997, upload-time = "2026-04-21T10:49:48.796Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ff/f01485fda6f4e5d441afb8dd5e7681e4db18826c1e271852f5d3957d6a80/pyarrow-24.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e6f1278ee4785b6db21229374a1c9e54ec7c549de5d1efc9630b6207de7e170b", size = 36678720, upload-time = "2026-04-21T10:49:55.858Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c2/2d2d5fea814237923f71b36495211f20b43a1576f9a4d6da7e751a64ec6f/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:adbbedc55506cbdabb830890444fb856bfb0060c46c6f8026c6c2f2cf86ae795", size = 45741852, upload-time = "2026-04-21T10:50:04.624Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3a/28ba9c1c1ebdbb5f1b94dfebb46f207e52e6a554b7fe4132540fde29a3a0/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ae8a1145af31d903fa9bb166824d7abe9b4681a000b0159c9fb99c11bc11ad26", size = 48889852, upload-time = "2026-04-21T10:50:12.293Z" }, + { url = "https://files.pythonhosted.org/packages/df/51/4a389acfd31dca009f8fb82d7f510bb4130f2b3a8e18cf00194d0687d8ac/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d7027eba1df3b2069e2e8d80f644fa0918b68c46432af3d088ddd390d063ecde", size = 49445207, upload-time = "2026-04-21T10:50:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/19/4b/0bab2b23d2ae901b1b9a03c0efd4b2d070256f8ce3fc43f6e58c167b2081/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e56a1ffe9bf7b727432b89104cc0849c21582949dd7bdcb34f17b2001a351a76", size = 51954117, upload-time = "2026-04-21T10:50:29.14Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/f4e9145da0417b3d2c12035a8492b35ff4a3dbc653e614fcfb51d9dedb38/pyarrow-24.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:38be1808cdd068605b787e6ca9119b27eb275a0234e50212c3492331680c3b1e", size = 28001155, upload-time = "2026-04-21T10:51:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/79/4f/46a49a63f43526da895b1a45bbb51d5baf8e4d77159f8528fc3e5490007f/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:418e48ce50a45a6a6c73c454677203a9c75c966cb1e92ca3370959185f197a05", size = 35250387, upload-time = "2026-04-21T10:50:35.552Z" }, + { url = "https://files.pythonhosted.org/packages/a0/da/d5e0cd5ef00796922404806d5f00325cdadc3441ce2c13fe7115f2df9a64/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2f16197705a230a78270cdd4ea8a1d57e86b2fdcbc34a1f6aebc72e65c986f9a", size = 36797102, upload-time = "2026-04-21T10:50:42.417Z" }, + { url = "https://files.pythonhosted.org/packages/34/c7/5904145b0a593a05236c882933d439b5720f0a145381179063722fbfc123/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fb24ac194bfc5e86839d7dcd52092ee31e5fe6733fe11f5e3b06ef0812b20072", size = 45745118, upload-time = "2026-04-21T10:50:49.324Z" }, + { url = "https://files.pythonhosted.org/packages/13/d3/cca42fe166d1c6e4d5b80e530b7949104d10e17508a90ae202dac205ce2a/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9700ebd9a51f5895ce75ff4ac4b3c47a7d4b42bc618be8e713e5d56bacf5f931", size = 48844765, upload-time = "2026-04-21T10:50:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/942c3b79878ba928324d1e17c274ed84581db8c0a749b24bcf4cbdf15bd3/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d8ddd2768da81d3ee08cfea9b597f4abb4e8e1dc8ae7e204b608d23a0d3ab699", size = 49471890, upload-time = "2026-04-21T10:51:02.439Z" }, + { url = "https://files.pythonhosted.org/packages/76/97/ff71431000a75d84135a1ace5ca4ba11726a231a8007bbb320a4c54075d5/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:61a3d7eaa97a14768b542f3d284dc6400dd2470d9f080708b13cd46b6ae18136", size = 51932250, upload-time = "2026-04-21T10:51:10.576Z" }, + { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, +] + +[[package]] +name = "pybase64" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/b8/4ed5c7ad5ec15b08d35cc79ace6145d5c1ae426e46435f4987379439dfea/pybase64-1.4.3.tar.gz", hash = "sha256:c2ed274c9e0ba9c8f9c4083cfe265e66dd679126cd9c2027965d807352f3f053", size = 137272, upload-time = "2025-12-06T13:27:04.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/63/21e981e9d3f1f123e0b0ee2130112b1956cad9752309f574862c7ae77c08/pybase64-1.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:70b0d4a4d54e216ce42c2655315378b8903933ecfa32fced453989a92b4317b2", size = 38237, upload-time = "2025-12-06T13:22:52.159Z" }, + { url = "https://files.pythonhosted.org/packages/92/fb/3f448e139516404d2a3963915cc10dc9dde7d3a67de4edba2f827adfef17/pybase64-1.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8127f110cdee7a70e576c5c9c1d4e17e92e76c191869085efbc50419f4ae3c72", size = 31673, upload-time = "2025-12-06T13:22:53.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/bb06a5b9885e7d853ac1e801c4d8abfdb4c8506deee33e53d55aa6690e67/pybase64-1.4.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f9ef0388878bc15a084bd9bf73ec1b2b4ee513d11009b1506375e10a7aae5032", size = 68331, upload-time = "2025-12-06T13:22:54.197Z" }, + { url = "https://files.pythonhosted.org/packages/64/15/8d60b9ec5e658185fc2ee3333e01a6e30d717cf677b24f47cbb3a859d13c/pybase64-1.4.3-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95a57cccf106352a72ed8bc8198f6820b16cc7d55aa3867a16dea7011ae7c218", size = 71370, upload-time = "2025-12-06T13:22:55.517Z" }, + { url = "https://files.pythonhosted.org/packages/ac/29/a3e5c1667cc8c38d025a4636855de0fc117fc62e2afeb033a3c6f12c6a22/pybase64-1.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cd1c47dfceb9c7bd3de210fb4e65904053ed2d7c9dce6d107f041ff6fbd7e21", size = 59834, upload-time = "2025-12-06T13:22:56.682Z" }, + { url = "https://files.pythonhosted.org/packages/a9/00/8ffcf9810bd23f3984698be161cf7edba656fd639b818039a7be1d6405d4/pybase64-1.4.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:9fe9922698f3e2f72874b26890d53a051c431d942701bb3a37aae94da0b12107", size = 56652, upload-time = "2025-12-06T13:22:57.724Z" }, + { url = "https://files.pythonhosted.org/packages/81/62/379e347797cdea4ab686375945bc77ad8d039c688c0d4d0cfb09d247beb9/pybase64-1.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:af5f4bd29c86b59bb4375e0491d16ec8a67548fa99c54763aaedaf0b4b5a6632", size = 59382, upload-time = "2025-12-06T13:22:58.758Z" }, + { url = "https://files.pythonhosted.org/packages/c6/f2/9338ffe2f487086f26a2c8ca175acb3baa86fce0a756ff5670a0822bb877/pybase64-1.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c302f6ca7465262908131411226e02100f488f531bb5e64cb901aa3f439bccd9", size = 59990, upload-time = "2025-12-06T13:23:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a4/85a6142b65b4df8625b337727aa81dc199642de3d09677804141df6ee312/pybase64-1.4.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:2f3f439fa4d7fde164ebbbb41968db7d66b064450ab6017c6c95cef0afa2b349", size = 54923, upload-time = "2025-12-06T13:23:02.369Z" }, + { url = "https://files.pythonhosted.org/packages/ac/00/e40215d25624012bf5b7416ca37f168cb75f6dd15acdb91ea1f2ea4dc4e7/pybase64-1.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a23c6866551043f8b681a5e1e0d59469148b2920a3b4fc42b1275f25ea4217a", size = 58664, upload-time = "2025-12-06T13:23:03.378Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/d7e19a63e795c13837f2356268d95dc79d1180e756f57ced742a1e52fdeb/pybase64-1.4.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:56e6526f8565642abc5f84338cc131ce298a8ccab696b19bdf76fa6d7dc592ef", size = 52338, upload-time = "2025-12-06T13:23:04.458Z" }, + { url = "https://files.pythonhosted.org/packages/f2/32/3c746d7a310b69bdd9df77ffc85c41b80bce00a774717596f869b0d4a20e/pybase64-1.4.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6a792a8b9d866ffa413c9687d9b611553203753987a3a582d68cbc51cf23da45", size = 68993, upload-time = "2025-12-06T13:23:05.526Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b3/63cec68f9d6f6e4c0b438d14e5f1ef536a5fe63ce14b70733ac5e31d7ab8/pybase64-1.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:62ad29a5026bb22cfcd1ca484ec34b0a5ced56ddba38ceecd9359b2818c9c4f9", size = 58055, upload-time = "2025-12-06T13:23:06.931Z" }, + { url = "https://files.pythonhosted.org/packages/d5/cb/7acf7c3c06f9692093c07f109668725dc37fb9a3df0fa912b50add645195/pybase64-1.4.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11b9d1d2d32ec358c02214363b8fc3651f6be7dd84d880ecd597a6206a80e121", size = 54430, upload-time = "2025-12-06T13:23:07.936Z" }, + { url = "https://files.pythonhosted.org/packages/33/39/4eb33ff35d173bfff4002e184ce8907f5d0a42d958d61cd9058ef3570179/pybase64-1.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0aebaa7f238caa0a0d373616016e2040c6c879ebce3ba7ab3c59029920f13640", size = 56272, upload-time = "2025-12-06T13:23:09.253Z" }, + { url = "https://files.pythonhosted.org/packages/19/97/a76d65c375a254e65b730c6f56bf528feca91305da32eceab8bcc08591e6/pybase64-1.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e504682b20c63c2b0c000e5f98a80ea867f8d97642e042a5a39818e44ba4d599", size = 70904, upload-time = "2025-12-06T13:23:10.336Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2c/8338b6d3da3c265002839e92af0a80d6db88385c313c73f103dfb800c857/pybase64-1.4.3-cp311-cp311-win32.whl", hash = "sha256:e9a8b81984e3c6fb1db9e1614341b0a2d98c0033d693d90c726677db1ffa3a4c", size = 33639, upload-time = "2025-12-06T13:23:11.9Z" }, + { url = "https://files.pythonhosted.org/packages/39/dc/32efdf2f5927e5449cc341c266a1bbc5fecd5319a8807d9c5405f76e6d02/pybase64-1.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:a90a8fa16a901fabf20de824d7acce07586e6127dc2333f1de05f73b1f848319", size = 35797, upload-time = "2025-12-06T13:23:13.174Z" }, + { url = "https://files.pythonhosted.org/packages/da/59/eda4f9cb0cbce5a45f0cd06131e710674f8123a4d570772c5b9694f88559/pybase64-1.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:61d87de5bc94d143622e94390ec3e11b9c1d4644fe9be3a81068ab0f91056f59", size = 31160, upload-time = "2025-12-06T13:23:15.696Z" }, + { url = "https://files.pythonhosted.org/packages/86/a7/efcaa564f091a2af7f18a83c1c4875b1437db56ba39540451dc85d56f653/pybase64-1.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18d85e5ab8b986bb32d8446aca6258ed80d1bafe3603c437690b352c648f5967", size = 38167, upload-time = "2025-12-06T13:23:16.821Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/c7ad35adff2d272bf2930132db2b3eea8c44bb1b1f64eb9b2b8e57cde7b4/pybase64-1.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f5791a3491d116d0deaf4d83268f48792998519698f8751efb191eac84320e9", size = 31673, upload-time = "2025-12-06T13:23:17.835Z" }, + { url = "https://files.pythonhosted.org/packages/43/1b/9a8cab0042b464e9a876d5c65fe5127445a2436da36fda64899b119b1a1b/pybase64-1.4.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f0b3f200c3e06316f6bebabd458b4e4bcd4c2ca26af7c0c766614d91968dee27", size = 68210, upload-time = "2025-12-06T13:23:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/62/f7/965b79ff391ad208b50e412b5d3205ccce372a2d27b7218ae86d5295b105/pybase64-1.4.3-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb632edfd132b3eaf90c39c89aa314beec4e946e210099b57d40311f704e11d4", size = 71599, upload-time = "2025-12-06T13:23:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/03/4b/a3b5175130b3810bbb8ccfa1edaadbd3afddb9992d877c8a1e2f274b476e/pybase64-1.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:356ef1d74648ce997f5a777cf8f1aefecc1c0b4fe6201e0ef3ec8a08170e1b54", size = 59922, upload-time = "2025-12-06T13:23:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/da/5d/c38d1572027fc601b62d7a407721688b04b4d065d60ca489912d6893e6cf/pybase64-1.4.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:c48361f90db32bacaa5518419d4eb9066ba558013aaf0c7781620279ecddaeb9", size = 56712, upload-time = "2025-12-06T13:23:22.77Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d4/4e04472fef485caa8f561d904d4d69210a8f8fc1608ea15ebd9012b92655/pybase64-1.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:702bcaa16ae02139d881aeaef5b1c8ffb4a3fae062fe601d1e3835e10310a517", size = 59300, upload-time = "2025-12-06T13:23:24.543Z" }, + { url = "https://files.pythonhosted.org/packages/86/e7/16e29721b86734b881d09b7e23dfd7c8408ad01a4f4c7525f3b1088e25ec/pybase64-1.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:53d0ffe1847b16b647c6413d34d1de08942b7724273dd57e67dcbdb10c574045", size = 60278, upload-time = "2025-12-06T13:23:25.608Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/18515f211d7c046be32070709a8efeeef8a0203de4fd7521e6b56404731b/pybase64-1.4.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9a1792e8b830a92736dae58f0c386062eb038dfe8004fb03ba33b6083d89cd43", size = 54817, upload-time = "2025-12-06T13:23:26.633Z" }, + { url = "https://files.pythonhosted.org/packages/e7/be/14e29d8e1a481dbff151324c96dd7b5d2688194bb65dc8a00ca0e1ad1e86/pybase64-1.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d468b1b1ac5ad84875a46eaa458663c3721e8be5f155ade356406848d3701f6", size = 58611, upload-time = "2025-12-06T13:23:27.684Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8a/a2588dfe24e1bbd742a554553778ab0d65fdf3d1c9a06d10b77047d142aa/pybase64-1.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e97b7bdbd62e71898cd542a6a9e320d9da754ff3ebd02cb802d69087ee94d468", size = 52404, upload-time = "2025-12-06T13:23:28.714Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/afcda7445bebe0cbc38cafdd7813234cdd4fc5573ff067f1abf317bb0cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b33aeaa780caaa08ffda87fc584d5eab61e3d3bbb5d86ead02161dc0c20d04bc", size = 68817, upload-time = "2025-12-06T13:23:30.079Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3a/87c3201e555ed71f73e961a787241a2438c2bbb2ca8809c29ddf938a3157/pybase64-1.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c0efcf78f11cf866bed49caa7b97552bc4855a892f9cc2372abcd3ed0056f0d", size = 57854, upload-time = "2025-12-06T13:23:31.17Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7d/931c2539b31a7b375e7d595b88401eeb5bd6c5ce1059c9123f9b608aaa14/pybase64-1.4.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:66e3791f2ed725a46593f8bd2761ff37d01e2cdad065b1dceb89066f476e50c6", size = 54333, upload-time = "2025-12-06T13:23:32.422Z" }, + { url = "https://files.pythonhosted.org/packages/de/5e/537601e02cc01f27e9d75f440f1a6095b8df44fc28b1eef2cd739aea8cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:72bb0b6bddadab26e1b069bb78e83092711a111a80a0d6b9edcb08199ad7299b", size = 56492, upload-time = "2025-12-06T13:23:33.515Z" }, + { url = "https://files.pythonhosted.org/packages/96/97/2a2e57acf8f5c9258d22aba52e71f8050e167b29ed2ee1113677c1b600c1/pybase64-1.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5b3365dbcbcdb0a294f0f50af0c0a16b27a232eddeeb0bceeefd844ef30d2a23", size = 70974, upload-time = "2025-12-06T13:23:36.27Z" }, + { url = "https://files.pythonhosted.org/packages/75/2e/a9e28941c6dab6f06e6d3f6783d3373044be9b0f9a9d3492c3d8d2260ac0/pybase64-1.4.3-cp312-cp312-win32.whl", hash = "sha256:7bca1ed3a5df53305c629ca94276966272eda33c0d71f862d2d3d043f1e1b91a", size = 33686, upload-time = "2025-12-06T13:23:37.848Z" }, + { url = "https://files.pythonhosted.org/packages/83/e3/507ab649d8c3512c258819c51d25c45d6e29d9ca33992593059e7b646a33/pybase64-1.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:9f2da8f56d9b891b18b4daf463a0640eae45a80af548ce435be86aa6eff3603b", size = 35833, upload-time = "2025-12-06T13:23:38.877Z" }, + { url = "https://files.pythonhosted.org/packages/bc/8a/6eba66cd549a2fc74bb4425fd61b839ba0ab3022d3c401b8a8dc2cc00c7a/pybase64-1.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:0631d8a2d035de03aa9bded029b9513e1fee8ed80b7ddef6b8e9389ffc445da0", size = 31185, upload-time = "2025-12-06T13:23:39.908Z" }, + { url = "https://files.pythonhosted.org/packages/3a/50/b7170cb2c631944388fe2519507fe3835a4054a6a12a43f43781dae82be1/pybase64-1.4.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:ea4b785b0607d11950b66ce7c328f452614aefc9c6d3c9c28bae795dc7f072e1", size = 33901, upload-time = "2025-12-06T13:23:40.951Z" }, + { url = "https://files.pythonhosted.org/packages/48/8b/69f50578e49c25e0a26e3ee72c39884ff56363344b79fc3967f5af420ed6/pybase64-1.4.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:6a10b6330188c3026a8b9c10e6b9b3f2e445779cf16a4c453d51a072241c65a2", size = 40807, upload-time = "2025-12-06T13:23:42.006Z" }, + { url = "https://files.pythonhosted.org/packages/5c/8d/20b68f11adfc4c22230e034b65c71392e3e338b413bf713c8945bd2ccfb3/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:27fdff227a0c0e182e0ba37a99109645188978b920dfb20d8b9c17eeee370d0d", size = 30932, upload-time = "2025-12-06T13:23:43.348Z" }, + { url = "https://files.pythonhosted.org/packages/f7/79/b1b550ac6bff51a4880bf6e089008b2e1ca16f2c98db5e039a08ac3ad157/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2a8204f1fdfec5aa4184249b51296c0de95445869920c88123978304aad42df1", size = 31394, upload-time = "2025-12-06T13:23:44.317Z" }, + { url = "https://files.pythonhosted.org/packages/82/70/b5d7c5932bf64ee1ec5da859fbac981930b6a55d432a603986c7f509c838/pybase64-1.4.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:874fc2a3777de6baf6aa921a7aa73b3be98295794bea31bd80568a963be30767", size = 38078, upload-time = "2025-12-06T13:23:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/56/fe/e66fe373bce717c6858427670736d54297938dad61c5907517ab4106bd90/pybase64-1.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2dc64a94a9d936b8e3449c66afabbaa521d3cc1a563d6bbaaa6ffa4535222e4b", size = 38158, upload-time = "2025-12-06T13:23:46.872Z" }, + { url = "https://files.pythonhosted.org/packages/80/a9/b806ed1dcc7aed2ea3dd4952286319e6f3a8b48615c8118f453948e01999/pybase64-1.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e48f86de1c145116ccf369a6e11720ce696c2ec02d285f440dfb57ceaa0a6cb4", size = 31672, upload-time = "2025-12-06T13:23:47.88Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c9/24b3b905cf75e23a9a4deaf203b35ffcb9f473ac0e6d8257f91a05dfce62/pybase64-1.4.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:1d45c8fe8fe82b65c36b227bb4a2cf623d9ada16bed602ce2d3e18c35285b72a", size = 68244, upload-time = "2025-12-06T13:23:49.026Z" }, + { url = "https://files.pythonhosted.org/packages/f8/cd/d15b0c3e25e5859fab0416dc5b96d34d6bd2603c1c96a07bb2202b68ab92/pybase64-1.4.3-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad70c26ba091d8f5167e9d4e1e86a0483a5414805cdb598a813db635bd3be8b8", size = 71620, upload-time = "2025-12-06T13:23:50.081Z" }, + { url = "https://files.pythonhosted.org/packages/0d/31/4ca953cc3dcde2b3711d6bfd70a6f4ad2ca95a483c9698076ba605f1520f/pybase64-1.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e98310b7c43145221e7194ac9fa7fffc84763c87bfc5e2f59f9f92363475bdc1", size = 59930, upload-time = "2025-12-06T13:23:51.68Z" }, + { url = "https://files.pythonhosted.org/packages/60/55/e7f7bdcd0fd66e61dda08db158ffda5c89a306bbdaaf5a062fbe4e48f4a1/pybase64-1.4.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:398685a76034e91485a28aeebcb49e64cd663212fd697b2497ac6dfc1df5e671", size = 56425, upload-time = "2025-12-06T13:23:52.732Z" }, + { url = "https://files.pythonhosted.org/packages/cb/65/b592c7f921e51ca1aca3af5b0d201a98666d0a36b930ebb67e7c2ed27395/pybase64-1.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7e46400a6461187ccb52ed75b0045d937529e801a53a9cd770b350509f9e4d50", size = 59327, upload-time = "2025-12-06T13:23:53.856Z" }, + { url = "https://files.pythonhosted.org/packages/23/95/1613d2fb82dbb1548595ad4179f04e9a8451bfa18635efce18b631eabe3f/pybase64-1.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1b62b9f2f291d94f5e0b76ab499790b7dcc78a009d4ceea0b0428770267484b6", size = 60294, upload-time = "2025-12-06T13:23:54.937Z" }, + { url = "https://files.pythonhosted.org/packages/9d/73/40431f37f7d1b3eab4673e7946ff1e8f5d6bd425ec257e834dae8a6fc7b0/pybase64-1.4.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:f30ceb5fa4327809dede614be586efcbc55404406d71e1f902a6fdcf322b93b2", size = 54858, upload-time = "2025-12-06T13:23:56.031Z" }, + { url = "https://files.pythonhosted.org/packages/a7/84/f6368bcaf9f743732e002a9858646fd7a54f428490d427dd6847c5cfe89e/pybase64-1.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0d5f18ed53dfa1d4cf8b39ee542fdda8e66d365940e11f1710989b3cf4a2ed66", size = 58629, upload-time = "2025-12-06T13:23:57.12Z" }, + { url = "https://files.pythonhosted.org/packages/43/75/359532f9adb49c6b546cafc65c46ed75e2ccc220d514ba81c686fbd83965/pybase64-1.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:119d31aa4b58b85a8ebd12b63c07681a138c08dfc2fe5383459d42238665d3eb", size = 52448, upload-time = "2025-12-06T13:23:58.298Z" }, + { url = "https://files.pythonhosted.org/packages/92/6c/ade2ba244c3f33ed920a7ed572ad772eb0b5f14480b72d629d0c9e739a40/pybase64-1.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3cf0218b0e2f7988cf7d738a73b6a1d14f3be6ce249d7c0f606e768366df2cce", size = 68841, upload-time = "2025-12-06T13:23:59.886Z" }, + { url = "https://files.pythonhosted.org/packages/a0/51/b345139cd236be382f2d4d4453c21ee6299e14d2f759b668e23080f8663f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:12f4ee5e988bc5c0c1106b0d8fc37fb0508f12dab76bac1b098cb500d148da9d", size = 57910, upload-time = "2025-12-06T13:24:00.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b8/9f84bdc4f1c4f0052489396403c04be2f9266a66b70c776001eaf0d78c1f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:937826bc7b6b95b594a45180e81dd4d99bd4dd4814a443170e399163f7ff3fb6", size = 54335, upload-time = "2025-12-06T13:24:02.046Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c7/be63b617d284de46578a366da77ede39c8f8e815ed0d82c7c2acca560fab/pybase64-1.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:88995d1460971ef80b13e3e007afbe4b27c62db0508bc7250a2ab0a0b4b91362", size = 56486, upload-time = "2025-12-06T13:24:03.141Z" }, + { url = "https://files.pythonhosted.org/packages/5e/96/f252c8f9abd6ded3ef1ccd3cdbb8393a33798007f761b23df8de1a2480e6/pybase64-1.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:72326fe163385ed3e1e806dd579d47fde5d8a59e51297a60fc4e6cbc1b4fc4ed", size = 70978, upload-time = "2025-12-06T13:24:04.221Z" }, + { url = "https://files.pythonhosted.org/packages/af/51/0f5714af7aeef96e30f968e4371d75ad60558aaed3579d7c6c8f1c43c18a/pybase64-1.4.3-cp313-cp313-win32.whl", hash = "sha256:b1623730c7892cf5ed0d6355e375416be6ef8d53ab9b284f50890443175c0ac3", size = 33684, upload-time = "2025-12-06T13:24:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ad/0cea830a654eb08563fb8214150ef57546ece1cc421c09035f0e6b0b5ea9/pybase64-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:8369887590f1646a5182ca2fb29252509da7ae31d4923dbb55d3e09da8cc4749", size = 35832, upload-time = "2025-12-06T13:24:06.35Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/eec2a8214989c751bc7b4cad1860eb2c6abf466e76b77508c0f488c96a37/pybase64-1.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:860b86bca71e5f0237e2ab8b2d9c4c56681f3513b1bf3e2117290c1963488390", size = 31175, upload-time = "2025-12-06T13:24:07.419Z" }, + { url = "https://files.pythonhosted.org/packages/db/c9/e23463c1a2913686803ef76b1a5ae7e6fac868249a66e48253d17ad7232c/pybase64-1.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eb51db4a9c93215135dccd1895dca078e8785c357fabd983c9f9a769f08989a9", size = 38497, upload-time = "2025-12-06T13:24:08.873Z" }, + { url = "https://files.pythonhosted.org/packages/71/83/343f446b4b7a7579bf6937d2d013d82f1a63057cf05558e391ab6039d7db/pybase64-1.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a03ef3f529d85fd46b89971dfb00c634d53598d20ad8908fb7482955c710329d", size = 32076, upload-time = "2025-12-06T13:24:09.975Z" }, + { url = "https://files.pythonhosted.org/packages/46/fc/cb64964c3b29b432f54d1bce5e7691d693e33bbf780555151969ffd95178/pybase64-1.4.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2e745f2ce760c6cf04d8a72198ef892015ddb89f6ceba489e383518ecbdb13ab", size = 72317, upload-time = "2025-12-06T13:24:11.129Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b7/fab2240da6f4e1ad46f71fa56ec577613cf5df9dce2d5b4cfaa4edd0e365/pybase64-1.4.3-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fac217cd9de8581a854b0ac734c50fd1fa4b8d912396c1fc2fce7c230efe3a7", size = 75534, upload-time = "2025-12-06T13:24:12.433Z" }, + { url = "https://files.pythonhosted.org/packages/91/3b/3e2f2b6e68e3d83ddb9fa799f3548fb7449765daec9bbd005a9fbe296d7f/pybase64-1.4.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:da1ee8fa04b283873de2d6e8fa5653e827f55b86bdf1a929c5367aaeb8d26f8a", size = 65399, upload-time = "2025-12-06T13:24:13.928Z" }, + { url = "https://files.pythonhosted.org/packages/6b/08/476ac5914c3b32e0274a2524fc74f01cbf4f4af4513d054e41574eb018f6/pybase64-1.4.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:b0bf8e884ee822ca7b1448eeb97fa131628fe0ff42f60cae9962789bd562727f", size = 60487, upload-time = "2025-12-06T13:24:15.177Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b8/618a92915330cc9cba7880299b546a1d9dab1a21fd6c0292ee44a4fe608c/pybase64-1.4.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1bf749300382a6fd1f4f255b183146ef58f8e9cb2f44a077b3a9200dfb473a77", size = 63959, upload-time = "2025-12-06T13:24:16.854Z" }, + { url = "https://files.pythonhosted.org/packages/a5/52/af9d8d051652c3051862c442ec3861259c5cdb3fc69774bc701470bd2a59/pybase64-1.4.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:153a0e42329b92337664cfc356f2065248e6c9a1bd651bbcd6dcaf15145d3f06", size = 64874, upload-time = "2025-12-06T13:24:18.328Z" }, + { url = "https://files.pythonhosted.org/packages/e4/51/5381a7adf1f381bd184d33203692d3c57cf8ae9f250f380c3fecbdbe554b/pybase64-1.4.3-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:86ee56ac7f2184ca10217ed1c655c1a060273e233e692e9086da29d1ae1768db", size = 58572, upload-time = "2025-12-06T13:24:19.417Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f0/578ee4ffce5818017de4fdf544e066c225bc435e73eb4793cde28a689d0b/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0e71a4db76726bf830b47477e7d830a75c01b2e9b01842e787a0836b0ba741e3", size = 63636, upload-time = "2025-12-06T13:24:20.497Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ad/8ae94814bf20159ea06310b742433e53d5820aa564c9fdf65bf2d79f8799/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2ba7799ec88540acd9861b10551d24656ca3c2888ecf4dba2ee0a71544a8923f", size = 56193, upload-time = "2025-12-06T13:24:21.559Z" }, + { url = "https://files.pythonhosted.org/packages/d1/31/6438cfcc3d3f0fa84d229fa125c243d5094e72628e525dfefadf3bcc6761/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2860299e4c74315f5951f0cf3e72ba0f201c3356c8a68f95a3ab4e620baf44e9", size = 72655, upload-time = "2025-12-06T13:24:22.673Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0d/2bbc9e9c3fc12ba8a6e261482f03a544aca524f92eae0b4908c0a10ba481/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:bb06015db9151f0c66c10aae8e3603adab6b6cd7d1f7335a858161d92fc29618", size = 62471, upload-time = "2025-12-06T13:24:23.8Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0b/34d491e7f49c1dbdb322ea8da6adecda7c7cd70b6644557c6e4ca5c6f7c7/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:242512a070817272865d37c8909059f43003b81da31f616bb0c391ceadffe067", size = 58119, upload-time = "2025-12-06T13:24:24.994Z" }, + { url = "https://files.pythonhosted.org/packages/ce/17/c21d0cde2a6c766923ae388fc1f78291e1564b0d38c814b5ea8a0e5e081c/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5d8277554a12d3e3eed6180ebda62786bf9fc8d7bb1ee00244258f4a87ca8d20", size = 60791, upload-time = "2025-12-06T13:24:26.046Z" }, + { url = "https://files.pythonhosted.org/packages/92/b2/eaa67038916a48de12b16f4c384bcc1b84b7ec731b23613cb05f27673294/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f40b7ddd698fc1e13a4b64fbe405e4e0e1279e8197e37050e24154655f5f7c4e", size = 74701, upload-time = "2025-12-06T13:24:27.466Z" }, + { url = "https://files.pythonhosted.org/packages/42/10/abb7757c330bb869ebb95dab0c57edf5961ffbd6c095c8209cbbf75d117d/pybase64-1.4.3-cp313-cp313t-win32.whl", hash = "sha256:46d75c9387f354c5172582a9eaae153b53a53afeb9c19fcf764ea7038be3bd8b", size = 33965, upload-time = "2025-12-06T13:24:28.548Z" }, + { url = "https://files.pythonhosted.org/packages/63/a0/2d4e5a59188e9e6aed0903d580541aaea72dcbbab7bf50fb8b83b490b6c3/pybase64-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:d7344625591d281bec54e85cbfdab9e970f6219cac1570f2aa140b8c942ccb81", size = 36207, upload-time = "2025-12-06T13:24:29.646Z" }, + { url = "https://files.pythonhosted.org/packages/1f/05/95b902e8f567b4d4b41df768ccc438af618f8d111e54deaf57d2df46bd76/pybase64-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:28a3c60c55138e0028313f2eccd321fec3c4a0be75e57a8d3eb883730b1b0880", size = 31505, upload-time = "2025-12-06T13:24:30.687Z" }, + { url = "https://files.pythonhosted.org/packages/e4/80/4bd3dff423e5a91f667ca41982dc0b79495b90ec0c0f5d59aca513e50f8c/pybase64-1.4.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:015bb586a1ea1467f69d57427abe587469392215f59db14f1f5c39b52fdafaf5", size = 33835, upload-time = "2025-12-06T13:24:31.767Z" }, + { url = "https://files.pythonhosted.org/packages/45/60/a94d94cc1e3057f602e0b483c9ebdaef40911d84a232647a2fe593ab77bb/pybase64-1.4.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:d101e3a516f837c3dcc0e5a0b7db09582ebf99ed670865223123fb2e5839c6c0", size = 40673, upload-time = "2025-12-06T13:24:32.82Z" }, + { url = "https://files.pythonhosted.org/packages/e3/71/cf62b261d431857e8e054537a5c3c24caafa331de30daede7b2c6c558501/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8f183ac925a48046abe047360fe3a1b28327afb35309892132fe1915d62fb282", size = 30939, upload-time = "2025-12-06T13:24:34.001Z" }, + { url = "https://files.pythonhosted.org/packages/24/3e/d12f92a3c1f7c6ab5d53c155bff9f1084ba997a37a39a4f781ccba9455f3/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30bf3558e24dcce4da5248dcf6d73792adfcf4f504246967e9db155be4c439ad", size = 31401, upload-time = "2025-12-06T13:24:35.11Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3d/9c27440031fea0d05146f8b70a460feb95d8b4e3d9ca8f45c972efb4c3d3/pybase64-1.4.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a674b419de318d2ce54387dd62646731efa32b4b590907800f0bd40675c1771d", size = 38075, upload-time = "2025-12-06T13:24:36.53Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d4/6c0e0cf0efd53c254173fbcd84a3d8fcbf5e0f66622473da425becec32a5/pybase64-1.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:720104fd7303d07bac302be0ff8f7f9f126f2f45c1edb4f48fdb0ff267e69fe1", size = 38257, upload-time = "2025-12-06T13:24:38.049Z" }, + { url = "https://files.pythonhosted.org/packages/50/eb/27cb0b610d5cd70f5ad0d66c14ad21c04b8db930f7139818e8fbdc14df4d/pybase64-1.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:83f1067f73fa5afbc3efc0565cecc6ed53260eccddef2ebe43a8ce2b99ea0e0a", size = 31685, upload-time = "2025-12-06T13:24:40.327Z" }, + { url = "https://files.pythonhosted.org/packages/db/26/b136a4b65e5c94ff06217f7726478df3f31ab1c777c2c02cf698e748183f/pybase64-1.4.3-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b51204d349a4b208287a8aa5b5422be3baa88abf6cc8ff97ccbda34919bbc857", size = 68460, upload-time = "2025-12-06T13:24:41.735Z" }, + { url = "https://files.pythonhosted.org/packages/68/6d/84ce50e7ee1ae79984d689e05a9937b2460d4efa1e5b202b46762fb9036c/pybase64-1.4.3-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30f2fd53efecbdde4bdca73a872a68dcb0d1bf8a4560c70a3e7746df973e1ef3", size = 71688, upload-time = "2025-12-06T13:24:42.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/57/6743e420416c3ff1b004041c85eb0ebd9c50e9cf05624664bfa1dc8b5625/pybase64-1.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0932b0c5cfa617091fd74f17d24549ce5de3628791998c94ba57be808078eeaf", size = 60040, upload-time = "2025-12-06T13:24:44.37Z" }, + { url = "https://files.pythonhosted.org/packages/3b/68/733324e28068a89119af2921ce548e1c607cc5c17d354690fc51c302e326/pybase64-1.4.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:acb61f5ab72bec808eb0d4ce8b87ec9f38d7d750cb89b1371c35eb8052a29f11", size = 56478, upload-time = "2025-12-06T13:24:45.815Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9e/f3f4aa8cfe3357a3cdb0535b78eb032b671519d3ecc08c58c4c6b72b5a91/pybase64-1.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:2bc2d5bc15168f5c04c53bdfe5a1e543b2155f456ed1e16d7edce9ce73842021", size = 59463, upload-time = "2025-12-06T13:24:46.938Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d1/53286038e1f0df1cf58abcf4a4a91b0f74ab44539c2547b6c31001ddd054/pybase64-1.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8a7bc3cd23880bdca59758bcdd6f4ef0674f2393782763910a7466fab35ccb98", size = 60360, upload-time = "2025-12-06T13:24:48.039Z" }, + { url = "https://files.pythonhosted.org/packages/00/9a/5cc6ce95db2383d27ff4d790b8f8b46704d360d701ab77c4f655bcfaa6a7/pybase64-1.4.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ad15acf618880d99792d71e3905b0e2508e6e331b76a1b34212fa0f11e01ad28", size = 54999, upload-time = "2025-12-06T13:24:49.547Z" }, + { url = "https://files.pythonhosted.org/packages/64/e7/c3c1d09c3d7ae79e3aa1358c6d912d6b85f29281e47aa94fc0122a415a2f/pybase64-1.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448158d417139cb4851200e5fee62677ae51f56a865d50cda9e0d61bda91b116", size = 58736, upload-time = "2025-12-06T13:24:50.641Z" }, + { url = "https://files.pythonhosted.org/packages/db/d5/0baa08e3d8119b15b588c39f0d39fd10472f0372e3c54ca44649cbefa256/pybase64-1.4.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9058c49b5a2f3e691b9db21d37eb349e62540f9f5fc4beabf8cbe3c732bead86", size = 52298, upload-time = "2025-12-06T13:24:51.791Z" }, + { url = "https://files.pythonhosted.org/packages/00/87/fc6f11474a1de7e27cd2acbb8d0d7508bda3efa73dfe91c63f968728b2a3/pybase64-1.4.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ce561724f6522907a66303aca27dce252d363fcd85884972d348f4403ba3011a", size = 69049, upload-time = "2025-12-06T13:24:53.253Z" }, + { url = "https://files.pythonhosted.org/packages/69/9d/7fb5566f669ac18b40aa5fc1c438e24df52b843c1bdc5da47d46d4c1c630/pybase64-1.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:63316560a94ac449fe86cb8b9e0a13714c659417e92e26a5cbf085cd0a0c838d", size = 57952, upload-time = "2025-12-06T13:24:54.342Z" }, + { url = "https://files.pythonhosted.org/packages/de/cc/ceb949232dbbd3ec4ee0190d1df4361296beceee9840390a63df8bc31784/pybase64-1.4.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7ecd796f2ac0be7b73e7e4e232b8c16422014de3295d43e71d2b19fd4a4f5368", size = 54484, upload-time = "2025-12-06T13:24:55.774Z" }, + { url = "https://files.pythonhosted.org/packages/a7/69/659f3c8e6a5d7b753b9c42a4bd9c42892a0f10044e9c7351a4148d413a33/pybase64-1.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d01e102a12fb2e1ed3dc11611c2818448626637857ec3994a9cf4809dfd23477", size = 56542, upload-time = "2025-12-06T13:24:57Z" }, + { url = "https://files.pythonhosted.org/packages/85/2c/29c9e6c9c82b72025f9676f9e82eb1fd2339ad038cbcbf8b9e2ac02798fc/pybase64-1.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ebff797a93c2345f22183f454fd8607a34d75eca5a3a4a969c1c75b304cee39d", size = 71045, upload-time = "2025-12-06T13:24:58.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/84/5a3dce8d7a0040a5c0c14f0fe1311cd8db872913fa04438071b26b0dac04/pybase64-1.4.3-cp314-cp314-win32.whl", hash = "sha256:28b2a1bb0828c0595dc1ea3336305cd97ff85b01c00d81cfce4f92a95fb88f56", size = 34200, upload-time = "2025-12-06T13:24:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/57/bc/ce7427c12384adee115b347b287f8f3cf65860b824d74fe2c43e37e81c1f/pybase64-1.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:33338d3888700ff68c3dedfcd49f99bfc3b887570206130926791e26b316b029", size = 36323, upload-time = "2025-12-06T13:25:01.708Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1b/2b8ffbe9a96eef7e3f6a5a7be75995eebfb6faaedc85b6da6b233e50c778/pybase64-1.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:62725669feb5acb186458da2f9353e88ae28ef66bb9c4c8d1568b12a790dfa94", size = 31584, upload-time = "2025-12-06T13:25:02.801Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d8/6824c2e6fb45b8fa4e7d92e3c6805432d5edc7b855e3e8e1eedaaf6efb7c/pybase64-1.4.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:153fe29be038948d9372c3e77ae7d1cab44e4ba7d9aaf6f064dbeea36e45b092", size = 38601, upload-time = "2025-12-06T13:25:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e5/10d2b3a4ad3a4850be2704a2f70cd9c0cf55725c8885679872d3bc846c67/pybase64-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7fe3decaa7c4a9e162327ec7bd81ce183d2b16f23c6d53b606649c6e0203e9e", size = 32078, upload-time = "2025-12-06T13:25:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/43/04/8b15c34d3c2282f1c1b0850f1113a249401b618a382646a895170bc9b5e7/pybase64-1.4.3-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a5ae04ea114c86eb1da1f6e18d75f19e3b5ae39cb1d8d3cd87c29751a6a22780", size = 72474, upload-time = "2025-12-06T13:25:06.434Z" }, + { url = "https://files.pythonhosted.org/packages/42/00/f34b4d11278f8fdc68bc38f694a91492aa318f7c6f1bd7396197ac0f8b12/pybase64-1.4.3-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1755b3dce3a2a5c7d17ff6d4115e8bee4a1d5aeae74469db02e47c8f477147da", size = 75706, upload-time = "2025-12-06T13:25:07.636Z" }, + { url = "https://files.pythonhosted.org/packages/bb/5d/71747d4ad7fe16df4c4c852bdbdeb1f2cf35677b48d7c34d3011a7a6ad3a/pybase64-1.4.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb852f900e27ffc4ec1896817535a0fa19610ef8875a096b59f21d0aa42ff172", size = 65589, upload-time = "2025-12-06T13:25:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/49/b1/d1e82bd58805bb5a3a662864800bab83a83a36ba56e7e3b1706c708002a5/pybase64-1.4.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:9cf21ea8c70c61eddab3421fbfce061fac4f2fb21f7031383005a1efdb13d0b9", size = 60670, upload-time = "2025-12-06T13:25:10.04Z" }, + { url = "https://files.pythonhosted.org/packages/15/67/16c609b7a13d1d9fc87eca12ba2dce5e67f949eeaab61a41bddff843cbb0/pybase64-1.4.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:afff11b331fdc27692fc75e85ae083340a35105cea1a3c4552139e2f0e0d174f", size = 64194, upload-time = "2025-12-06T13:25:11.48Z" }, + { url = "https://files.pythonhosted.org/packages/3c/11/37bc724e42960f0106c2d33dc957dcec8f760c91a908cc6c0df7718bc1a8/pybase64-1.4.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9a5143df542c1ce5c1f423874b948c4d689b3f05ec571f8792286197a39ba02", size = 64984, upload-time = "2025-12-06T13:25:12.645Z" }, + { url = "https://files.pythonhosted.org/packages/6e/66/b2b962a6a480dd5dae3029becf03ea1a650d326e39bf1c44ea3db78bb010/pybase64-1.4.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:d62e9861019ad63624b4a7914dff155af1cc5d6d79df3be14edcaedb5fdad6f9", size = 58750, upload-time = "2025-12-06T13:25:13.848Z" }, + { url = "https://files.pythonhosted.org/packages/2b/15/9b6d711035e29b18b2e1c03d47f41396d803d06ef15b6c97f45b75f73f04/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:84cfd4d92668ef5766cc42a9c9474b88960ac2b860767e6e7be255c6fddbd34a", size = 63816, upload-time = "2025-12-06T13:25:15.356Z" }, + { url = "https://files.pythonhosted.org/packages/b4/21/e2901381ed0df62e2308380f30d9c4d87d6b74e33a84faed3478d33a7197/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:60fc025437f9a7c2cc45e0c19ed68ed08ba672be2c5575fd9d98bdd8f01dd61f", size = 56348, upload-time = "2025-12-06T13:25:16.559Z" }, + { url = "https://files.pythonhosted.org/packages/c4/16/3d788388a178a0407aa814b976fe61bfa4af6760d9aac566e59da6e4a8b4/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:edc8446196f04b71d3af76c0bd1fe0a45066ac5bffecca88adb9626ee28c266f", size = 72842, upload-time = "2025-12-06T13:25:18.055Z" }, + { url = "https://files.pythonhosted.org/packages/a6/63/c15b1f8bd47ea48a5a2d52a4ec61f037062932ea6434ab916107b58e861e/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e99f6fa6509c037794da57f906ade271f52276c956d00f748e5b118462021d48", size = 62651, upload-time = "2025-12-06T13:25:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b8/f544a2e37c778d59208966d4ef19742a0be37c12fc8149ff34483c176616/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d94020ef09f624d841aa9a3a6029df8cf65d60d7a6d5c8687579fa68bd679b65", size = 58295, upload-time = "2025-12-06T13:25:20.822Z" }, + { url = "https://files.pythonhosted.org/packages/03/99/1fae8a3b7ac181e36f6e7864a62d42d5b1f4fa7edf408c6711e28fba6b4d/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f64ce70d89942a23602dee910dec9b48e5edf94351e1b378186b74fcc00d7f66", size = 60960, upload-time = "2025-12-06T13:25:22.099Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9e/cd4c727742345ad8384569a4466f1a1428f4e5cc94d9c2ab2f53d30be3fe/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8ea99f56e45c469818b9781903be86ba4153769f007ba0655fa3b46dc332803d", size = 74863, upload-time = "2025-12-06T13:25:23.442Z" }, + { url = "https://files.pythonhosted.org/packages/28/86/a236ecfc5b494e1e922da149689f690abc84248c7c1358f5605b8c9fdd60/pybase64-1.4.3-cp314-cp314t-win32.whl", hash = "sha256:343b1901103cc72362fd1f842524e3bb24978e31aea7ff11e033af7f373f66ab", size = 34513, upload-time = "2025-12-06T13:25:24.592Z" }, + { url = "https://files.pythonhosted.org/packages/56/ce/ca8675f8d1352e245eb012bfc75429ee9cf1f21c3256b98d9a329d44bf0f/pybase64-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:57aff6f7f9dea6705afac9d706432049642de5b01080d3718acc23af87c5af76", size = 36702, upload-time = "2025-12-06T13:25:25.72Z" }, + { url = "https://files.pythonhosted.org/packages/3b/30/4a675864877397179b09b720ee5fcb1cf772cf7bebc831989aff0a5f79c1/pybase64-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e906aa08d4331e799400829e0f5e4177e76a3281e8a4bc82ba114c6b30e405c9", size = 31904, upload-time = "2025-12-06T13:25:26.826Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7c/545fd4935a0e1ddd7147f557bf8157c73eecec9cffd523382fa7af2557de/pybase64-1.4.3-graalpy311-graalpy242_311_native-macosx_10_9_x86_64.whl", hash = "sha256:d27c1dfdb0c59a5e758e7a98bd78eaca5983c22f4a811a36f4f980d245df4611", size = 38393, upload-time = "2025-12-06T13:26:19.535Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ca/ae7a96be9ddc96030d4e9dffc43635d4e136b12058b387fd47eb8301b60f/pybase64-1.4.3-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0f1a0c51d6f159511e3431b73c25db31095ee36c394e26a4349e067c62f434e5", size = 32109, upload-time = "2025-12-06T13:26:20.72Z" }, + { url = "https://files.pythonhosted.org/packages/bf/44/d4b7adc7bf4fd5b52d8d099121760c450a52c390223806b873f0b6a2d551/pybase64-1.4.3-graalpy311-graalpy242_311_native-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a492518f3078a4e3faaef310697d21df9c6bc71908cebc8c2f6fbfa16d7d6b1f", size = 43227, upload-time = "2025-12-06T13:26:21.845Z" }, + { url = "https://files.pythonhosted.org/packages/08/86/2ba2d8734ef7939debeb52cf9952e457ba7aa226cae5c0e6dd631f9b851f/pybase64-1.4.3-graalpy311-graalpy242_311_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae1a0f47784fd16df90d8acc32011c8d5fcdd9ab392c9ec49543e5f6a9c43a4", size = 35804, upload-time = "2025-12-06T13:26:23.149Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5b/19c725dc3aaa6281f2ce3ea4c1628d154a40dd99657d1381995f8096768b/pybase64-1.4.3-graalpy311-graalpy242_311_native-win_amd64.whl", hash = "sha256:03cea70676ffbd39a1ab7930a2d24c625b416cacc9d401599b1d29415a43ab6a", size = 35880, upload-time = "2025-12-06T13:26:24.663Z" }, + { url = "https://files.pythonhosted.org/packages/17/45/92322aec1b6979e789b5710f73c59f2172bc37c8ce835305434796824b7b/pybase64-1.4.3-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:2baaa092f3475f3a9c87ac5198023918ea8b6c125f4c930752ab2cbe3cd1d520", size = 38746, upload-time = "2025-12-06T13:26:25.869Z" }, + { url = "https://files.pythonhosted.org/packages/11/94/f1a07402870388fdfc2ecec0c718111189732f7d0f2d7fe1386e19e8fad0/pybase64-1.4.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:cde13c0764b1af07a631729f26df019070dad759981d6975527b7e8ecb465b6c", size = 32573, upload-time = "2025-12-06T13:26:27.792Z" }, + { url = "https://files.pythonhosted.org/packages/fa/8f/43c3bb11ca9bacf81cb0b7a71500bb65b2eda6d5fe07433c09b543de97f3/pybase64-1.4.3-graalpy312-graalpy250_312_native-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5c29a582b0ea3936d02bd6fe9bf674ab6059e6e45ab71c78404ab2c913224414", size = 43461, upload-time = "2025-12-06T13:26:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4c/2a5258329200be57497d3972b5308558c6de42e3749c6cc2aa1cbe34b25a/pybase64-1.4.3-graalpy312-graalpy250_312_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6b664758c804fa919b4f1257aa8cf68e95db76fc331de5f70bfc3a34655afe1", size = 36058, upload-time = "2025-12-06T13:26:30.092Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6d/41faa414cde66ec023b0ca8402a8f11cb61731c3dc27c082909cbbd1f929/pybase64-1.4.3-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:f7537fa22ae56a0bf51e4b0ffc075926ad91c618e1416330939f7ef366b58e3b", size = 36231, upload-time = "2025-12-06T13:26:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/160dded493c00d3376d4ad0f38a2119c5345de4a6693419ad39c3565959b/pybase64-1.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:277de6e03cc9090fb359365c686a2a3036d23aee6cd20d45d22b8c89d1247f17", size = 37939, upload-time = "2025-12-06T13:26:41.014Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b8/a0f10be8d648d6f8f26e560d6e6955efa7df0ff1e009155717454d76f601/pybase64-1.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab1dd8b1ed2d1d750260ed58ab40defaa5ba83f76a30e18b9ebd5646f6247ae5", size = 31466, upload-time = "2025-12-06T13:26:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/d3/22/832a2f9e76cdf39b52e01e40d8feeb6a04cf105494f2c3e3126d0149717f/pybase64-1.4.3-pp311-pypy311_pp73-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:bd4d2293de9fd212e294c136cec85892460b17d24e8c18a6ba18750928037750", size = 40681, upload-time = "2025-12-06T13:26:43.782Z" }, + { url = "https://files.pythonhosted.org/packages/12/d7/6610f34a8972415fab3bb4704c174a1cc477bffbc3c36e526428d0f3957d/pybase64-1.4.3-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af6d0d3a691911cc4c9a625f3ddcd3af720738c21be3d5c72de05629139d393", size = 41294, upload-time = "2025-12-06T13:26:44.936Z" }, + { url = "https://files.pythonhosted.org/packages/64/25/ed24400948a6c974ab1374a233cb7e8af0a5373cea0dd8a944627d17c34a/pybase64-1.4.3-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5cfc8c49a28322d82242088378f8542ce97459866ba73150b062a7073e82629d", size = 35447, upload-time = "2025-12-06T13:26:46.098Z" }, + { url = "https://files.pythonhosted.org/packages/ee/2b/e18ee7c5ee508a82897f021c1981533eca2940b5f072fc6ed0906c03a7a7/pybase64-1.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:debf737e09b8bf832ba86f5ecc3d3dbd0e3021d6cd86ba4abe962d6a5a77adb3", size = 36134, upload-time = "2025-12-06T13:26:47.35Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pymupdf" +version = "1.27.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/32/708bedc9dde7b328d45abbc076091769d44f2f24ad151ad92d56a6ec142b/pymupdf-1.27.2.3.tar.gz", hash = "sha256:7a92faa25129e8bbec5e50eeb9214f187665428c31b05c4ef6e36c58c0b1c6d2", size = 85759618, upload-time = "2026-04-24T14:13:14.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/09/ddbdfa7ee91fbabd6f63d7d744884cbdfe3e7ff9b8604749fb38bddf5c5d/pymupdf-1.27.2.3-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc1bc3cae6e9e150b0dbb0a9221bdfd411d65f0db2fe359eaa22467d7cc2a05f", size = 24002636, upload-time = "2026-04-24T14:09:17.459Z" }, + { url = "https://files.pythonhosted.org/packages/01/89/3f8edd6c4f50ca370e2a2f2a3011face36f3760728ffe76dffec91c0fca0/pymupdf-1.27.2.3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:660d93cb6da5bbddf11d3982ae27745dd3a9902d9f24cdb69adab83962294b5a", size = 23278238, upload-time = "2026-04-24T14:09:32.882Z" }, + { url = "https://files.pythonhosted.org/packages/c3/26/b7e5a70eb83bd189f8b5df87ec442746b992f2f632662839b288170d357d/pymupdf-1.27.2.3-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1dd460a3ae4597a755f00a3bd9771f5ebf1531dc111f6a36bf05dd00a6b84425", size = 24333923, upload-time = "2026-04-24T14:09:47.341Z" }, + { url = "https://files.pythonhosted.org/packages/e4/a0/aa1ee2240f29481a04a827c313333b4ecd8a14d6ac3e15d3f41a30574781/pymupdf-1.27.2.3-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:857842b4888827bd6155a1131341b2822a7ebe9a8c15a975fd7d490d7a64a30c", size = 24963198, upload-time = "2026-04-24T14:10:07.408Z" }, + { url = "https://files.pythonhosted.org/packages/69/49/4f742451f980840829fc00ba158bebb25d389c846d8f4f8c65936ee55de8/pymupdf-1.27.2.3-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:580983849c64a08d08344ca3d1580e87c01f046a8392421797bc850efd72a5b6", size = 25184609, upload-time = "2026-04-24T14:10:22.911Z" }, + { url = "https://files.pythonhosted.org/packages/f6/3f/3853d6608f394faf6eec2bd4e8ea9f6a00beea329b071abdb29f4164cc3d/pymupdf-1.27.2.3-cp310-abi3-win32.whl", hash = "sha256:a5c1088a87189891a4946ab314a14b7934ac4c5b6077f7e74ebee956f8906d0e", size = 18019286, upload-time = "2026-04-24T14:10:34.239Z" }, + { url = "https://files.pythonhosted.org/packages/44/47/5fb10fe73f96b31253a41647c362ea9e0380920bddf16028414a051247fc/pymupdf-1.27.2.3-cp310-abi3-win_amd64.whl", hash = "sha256:d20f68ef15195e073071dbc4ae7455257c7889af7584e39df490c0a92728526e", size = 19249102, upload-time = "2026-04-24T14:10:46.72Z" }, + { url = "https://files.pythonhosted.org/packages/53/a4/b9e91aac82293f9c954654c85581ee8212b5b05efadc534b581141241e6f/pymupdf-1.27.2.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:77691604c5d1d0233827139bbcdea61fd57879c84712b8e49b1f45520f7ab9c2", size = 25000393, upload-time = "2026-04-24T14:11:01.669Z" }, +] + +[[package]] +name = "pypdfium2" +version = "5.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/78/d9b45abb97a3686643f7c6472a5f7688f2013a373226121dc76b9debbacf/pypdfium2-5.10.1.tar.gz", hash = "sha256:f257d2011eb43c846b7e9f5a802e28646b29732763e4a35dd6ca76f9be580538", size = 272963, upload-time = "2026-06-15T10:09:16.179Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/49/0b85fcc0d236582143a25cf275b0b4f5d786f51eb07a89ec9c79d43efe18/pypdfium2-5.10.1-py3-none-android_23_arm64_v8a.whl", hash = "sha256:13abf7a9f5e0ddebc8bbcccea5f13ae5abe8a298ea219e125b0fc24c1d2171b4", size = 3409176, upload-time = "2026-06-15T10:08:36.017Z" }, + { url = "https://files.pythonhosted.org/packages/20/d2/2f522c5b2ad5166edf256bb4dbab97de5d07b2573f8a5630ddfcd1f8d4ee/pypdfium2-5.10.1-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:a0dc52b56631e2f7edcdb22bae3b155aa840bec32c5bd05781e90baaecee88e7", size = 2866175, upload-time = "2026-06-15T10:08:37.955Z" }, + { url = "https://files.pythonhosted.org/packages/a7/be/477548c026c2badfdbf4afc3358b7135121fb5bec2e2effcb67e3a674d0b/pypdfium2-5.10.1-py3-none-macosx_12_0_arm64.whl", hash = "sha256:ebb9e63f92d15fc41b359fe7a187233dfae37548800e1fa09cb2fc466ac89951", size = 3621427, upload-time = "2026-06-15T10:08:39.969Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9b/0131c7f711b62c6edd6b200e9eb6340be6de4f6dc5baae625e3394d8d5fb/pypdfium2-5.10.1-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:d04f2050b6b32bb18624688b600543342a4ab3aacf64bf66521a5af72bbc7de1", size = 3682825, upload-time = "2026-06-15T10:08:42.181Z" }, + { url = "https://files.pythonhosted.org/packages/9d/3d/bddfceb6e67e54d6dd1ab6c0f1feff796a89596e40f6345a3b4cc6a3d408/pypdfium2-5.10.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0de53d2710ca9509fc2812340acc57c3f043697609068592d87de654f8cabf44", size = 3682206, upload-time = "2026-06-15T10:08:44.087Z" }, + { url = "https://files.pythonhosted.org/packages/58/08/dedeb25c6645fc8a5eda24f54e0b1d083b7334ababf5f518bb939d729cc0/pypdfium2-5.10.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:10a26ce04795f8ec079e81c707fcb5737061e8a78025babc3d6e36642e9c903a", size = 3413720, upload-time = "2026-06-15T10:08:45.739Z" }, + { url = "https://files.pythonhosted.org/packages/cc/48/69e1fc8b1216005243c6415183bbf6de1cda3f5a06758b3fa4a26a7385c6/pypdfium2-5.10.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be6d2a8d1bcfd777188e7aa55a25c83f34d54e8350ad8810fb32017787d9b0d9", size = 3812913, upload-time = "2026-06-15T10:08:47.45Z" }, + { url = "https://files.pythonhosted.org/packages/72/7f/132455a58ad736d76815c6cd1307532c3f433299d945b9d2f8cc2387c309/pypdfium2-5.10.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf4d2527f79f31c550490cc74c9f32e19385a944630a3ef4cd4d9b6f961fbf77", size = 4223220, upload-time = "2026-06-15T10:08:49.099Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8c/4d5804eca598bbe894e0a9a510807e221c1623e7276ce6b68fa2660dc933/pypdfium2-5.10.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ba127750bc3f4161461538d532d74491cd976f584f1753a6cee9cb821338ec1", size = 3738950, upload-time = "2026-06-15T10:08:50.821Z" }, + { url = "https://files.pythonhosted.org/packages/d1/7f/baac59bf14ff914d97789ee0368c22ae233aa857aa9c0726bcb515dbc4d7/pypdfium2-5.10.1-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:80f30517ee089dfbbc6e9de6da365b6e8c0ce8f80c40b7d4025a3b1d3bbd8a70", size = 4029869, upload-time = "2026-06-15T10:08:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d7/a5d58a0bcba31a0e37ed636a76ef3d2d215733f28af61637287d061b0c54/pypdfium2-5.10.1-py3-none-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ad5f5de15febb788c6eb3853e58aceda9cd8c5187c92472abaeecf9558deb0cf", size = 3990927, upload-time = "2026-06-15T10:08:54.555Z" }, + { url = "https://files.pythonhosted.org/packages/da/1a/98eebd14b36812176297cf765d504ebeebc982f895c8a3a9fbd2717797de/pypdfium2-5.10.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8947fa3cd808da33960bdf8ff9e5247aa94ccd94f0b09bb3402e99498d83bcc9", size = 4989624, upload-time = "2026-06-15T10:08:56.284Z" }, + { url = "https://files.pythonhosted.org/packages/1c/20/f2e124d607b8bb90a9f1ce976afff38c70e215cd7ea86af784cb2e8a19dd/pypdfium2-5.10.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:139a6387a3a2652f288e53164268bb03af4fa221d78484ee18407053a60082a3", size = 4535124, upload-time = "2026-06-15T10:08:58.214Z" }, + { url = "https://files.pythonhosted.org/packages/59/ef/469ea87f668a32ff3280ea15e522e3a7858d2c80f1ecc320ece1244624c9/pypdfium2-5.10.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:172ff3e10358d66456e27fb0b8b5098e28ec24e51072eb4b5b86077d550e21bb", size = 5229373, upload-time = "2026-06-15T10:09:00.702Z" }, + { url = "https://files.pythonhosted.org/packages/c3/48/c7ed3001f0c5e28114c98bf918c8121e422a65ba7323e1e12f3f28e2d278/pypdfium2-5.10.1-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:09fda0609dc4749c9865a0315c447d6f2583a580693bcabd69980d3fbb22ad51", size = 5140010, upload-time = "2026-06-15T10:09:02.734Z" }, + { url = "https://files.pythonhosted.org/packages/b2/bc/00b731bfc1fdc0f3c7d91108fceb54978ff4f5a92336820e5ed6c12535ff/pypdfium2-5.10.1-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:6b01adcfe9aaf7a635a59bed5687fd4fd7b0da292664f050d4ebd2bfa5c70584", size = 4643310, upload-time = "2026-06-15T10:09:04.794Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e8/3ad242233f657c19092a8c83684c8154b8d02a826535a6a2591d91aa5dde/pypdfium2-5.10.1-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:610e14c37d2090b826dccc0604fc7e7612c0ce591190780ae228abdb9abb971e", size = 5087879, upload-time = "2026-06-15T10:09:06.861Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/9d0747a02ac3021ed3db7ac27c5187d97e78b0253a4bdfbf386666968474/pypdfium2-5.10.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f2803020952afa57e1e148adc19369b835154e1fa251a1ca85008ab6466a710f", size = 5047369, upload-time = "2026-06-15T10:09:08.883Z" }, + { url = "https://files.pythonhosted.org/packages/9e/41/7d5187e9527eae81890a3b13193442112ef788d61ddaa68a6d59ac447bec/pypdfium2-5.10.1-py3-none-win32.whl", hash = "sha256:8702bb4f01ddfc8e7757b41b4c2c8392ac17c9f0234476e1e69672ea7c6d6aa0", size = 3680056, upload-time = "2026-06-15T10:09:10.95Z" }, + { url = "https://files.pythonhosted.org/packages/16/1d/c62bd59dd8345cc4b640f942f465633f6b07b859d01ddb648610a7bf5c7c/pypdfium2-5.10.1-py3-none-win_amd64.whl", hash = "sha256:58da5b51fb7884c7d21a05062ab13edb011d1a08dfd9694f3d5d685df62796b9", size = 3812105, upload-time = "2026-06-15T10:09:12.684Z" }, + { url = "https://files.pythonhosted.org/packages/10/d5/21bac39125df8a93e99c04583486b58a62b5997d6b3541e3ad0f69053392/pypdfium2-5.10.1-py3-none-win_arm64.whl", hash = "sha256:e3301c2f7a66fb8cb57dba857d0c9e90215e178f6602a87c5a306cd98513dab8", size = 3600043, upload-time = "2026-06-15T10:09:14.606Z" }, +] + +[[package]] +name = "pypika" +version = "0.51.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/78/cbaebba88e05e2dcda13ca203131b38d3640219f20ebb49676d26714861b/pypika-0.51.1.tar.gz", hash = "sha256:c30c7c1048fbf056fd3920c5a2b88b0c29dd190a9b2bee971fd17e4abe4d0ebe", size = 80919, upload-time = "2026-02-04T11:27:48.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/83/c77dfeed04022e8930b08eedca2b6e5efed256ab3321396fde90066efb65/pypika-0.51.1-py2.py3-none-any.whl", hash = "sha256:77985b4d7ce71b9905255bf12468cf598349e98837c037541cfc240e528aec46", size = 60585, upload-time = "2026-02-04T11:27:46.251Z" }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "pytest-agentic-fabric" +version = "0.1.0" +source = { editable = "packages/pytest-agentic-fabric" } +dependencies = [ + { name = "pytest" }, +] + +[package.optional-dependencies] +dev = [ + { name = "mypy" }, + { name = "pytest" }, + { name = "ruff" }, +] +tests = [ + { name = "pytest" }, +] +typing = [ + { name = "mypy" }, +] + +[package.metadata] +requires-dist = [ + { name = "mypy", marker = "extra == 'typing'", specifier = ">=1.20.1" }, + { name = "pytest", specifier = ">=9.0.3" }, + { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.0.3" }, + { name = "pytest-agentic-fabric", extras = ["tests", "typing"], marker = "extra == 'dev'" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" }, +] +provides-extras = ["tests", "typing", "dev"] + +[[package]] +name = "pytest-asyncio" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, +] + +[[package]] +name = "pytest-timeout" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-docx" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256, upload-time = "2025-06-16T20:46:27.921Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987, upload-time = "2025-06-16T20:46:22.506Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.32" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, +] + +[[package]] +name = "pytube" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/e7/16fec46c8d255c4bbc4b185d89c91dc92cdb802836570d8004d0db169c91/pytube-15.0.0.tar.gz", hash = "sha256:076052efe76f390dfa24b1194ff821d4e86c17d41cb5562f3a276a8bcbfc9d1d", size = 67229, upload-time = "2023-05-07T19:39:01.903Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/64/bcf8632ed2b7a36bbf84a0544885ffa1d0b4bcf25cc0903dba66ec5fdad9/pytube-15.0.0-py3-none-any.whl", hash = "sha256:07b9904749e213485780d7eb606e5e5b8e4341aa4dccf699160876da00e12d78", size = 57594, upload-time = "2023-05-07T19:38:59.191Z" }, +] + +[[package]] +name = "pywin32" +version = "312" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/f5/10a6e845a00fc5e7afd0a988b744f403d4d57162a28d160a093c4d9322f0/pywin32-312-cp311-cp311-win32.whl", hash = "sha256:17948aeadbdb091f0ced6ef0841620794e68327b94ee415571c1203594b7215c", size = 6362659, upload-time = "2026-06-04T07:49:21.349Z" }, + { url = "https://files.pythonhosted.org/packages/35/c4/dcd2d62b5944b6d5db53413a5899016ccd57ffcb7278f3f81655d25d2027/pywin32-312-cp311-cp311-win_amd64.whl", hash = "sha256:d11417d84412f859b722fad0841b3614459ed0047f7542d8362e77884f6b6e8a", size = 6928825, upload-time = "2026-06-04T07:49:23.934Z" }, + { url = "https://files.pythonhosted.org/packages/b7/56/3cbb433fe4501cdba2eb9040f56a4e1a8243faa4186b25295564d1a7a79d/pywin32-312-cp311-cp311-win_arm64.whl", hash = "sha256:b2200a054ca6d6625c4842fc56a4976a4b47f96b73dbe5538c3f813a80359f47", size = 6721875, upload-time = "2026-06-04T07:49:26.416Z" }, + { url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" }, + { url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" }, + { url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" }, + { url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159, upload-time = "2026-06-04T07:49:50.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293, upload-time = "2026-06-04T07:49:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { 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/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "regex" +version = "2026.5.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/0e/49aee608ad09480e7fd276898c99ec6192985fa331abe4eb3a986094490b/regex-2026.5.9.tar.gz", hash = "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270", size = 416074, upload-time = "2026-05-09T23:15:19.37Z" } +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/03/d2/59f01110660081cce9c0bc30ebd0b5ee250dacf658e3248ed92f01e0e8ee/regex-2026.5.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46f1326ca6e65b0879d23ca302c0f2415aad42ff0309b9c818e7949fe19a41d8", size = 291271, upload-time = "2026-05-09T23:12:07.731Z" }, + { 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/ce/fc/294fe4fac4f2ed67207b17471815870c1c45b3a489e08e0ac96daea16ef6/regex-2026.5.9-cp311-cp311-win32.whl", hash = "sha256:8676474c07469d6f33dd1085ca2cd45f65785f32518f2b20e36d9953ca07f994", size = 266249, upload-time = "2026-05-09T23:12:30.141Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b0/8dce459f6245bcf8f6e9f23ac9569f1a0f15c131cc0745e82b43226204cf/regex-2026.5.9-cp311-cp311-win_amd64.whl", hash = "sha256:246de9d60aa3f8538b519834dd95cbf276ea263d6a7bd5a3666dc3fa0230505b", size = 278423, upload-time = "2026-05-09T23:12:31.676Z" }, + { url = "https://files.pythonhosted.org/packages/db/8d/f9aeff6ad63a3ef720386f2907e6d34a35a510a6e498ebad28b0fb3f6ab6/regex-2026.5.9-cp311-cp311-win_arm64.whl", hash = "sha256:d726ca3f0d76969bf1e8e477d160d3d666bbf999f6860bd314889e5345782046", size = 270420, upload-time = "2026-05-09T23:12:33.194Z" }, + { 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/1e/95/fc7ba4303b5a0f92446a12ee6778ef2c6c799233f5060042a31bf390cfe9/regex-2026.5.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:398c521292f4c7fb807001dcd54694d3a1fcafc179a36ad9cc56f98df85930b6", size = 292112, upload-time = "2026-05-09T23:12:36.285Z" }, + { 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/c3/1c/bdcc98f9a4af4fdd166c74941174619ccff4726d3ce32faa8e9a2ecd38dd/regex-2026.5.9-cp312-cp312-win32.whl", hash = "sha256:164eba9b755ea6f244b0d881196fbc1fac09714e9782c9e2732b813142033c8e", size = 266699, upload-time = "2026-05-09T23:12:59.14Z" }, + { url = "https://files.pythonhosted.org/packages/78/87/240d36864f9e48ace85f72e79ced97ceb7f27ce87739a947dcb834b4e6bc/regex-2026.5.9-cp312-cp312-win_amd64.whl", hash = "sha256:86f40a5d6444db30a125c9c9177e6b25dad981cbc37451fd838f145e6edac92e", size = 277783, upload-time = "2026-05-09T23:13:00.789Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b5/7b30f312b0669dff5beebe5b0989dc2d1a312b1a44fab852199c387a5b96/regex-2026.5.9-cp312-cp312-win_arm64.whl", hash = "sha256:96f5f58b54a063d7ea9dca08e1cf57bfe10499c4d579ee672da284f57f5f0070", size = 270513, upload-time = "2026-05-09T23:13:02.426Z" }, + { 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/44/da/bf30abaaa737b58f4a4b8c4a03659e02fd92092c822e0197ed9e0daab917/regex-2026.5.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d7bdc0ab8f3dd7e1b4f9ab88634e13374669db86bb3c72e8292f07ae313f539f", size = 292019, upload-time = "2026-05-09T23:13:06.022Z" }, + { 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/05/a4/018e71f7d2ad48c1ebe6d3ae0026f9b7cb4802fd15c7cc02fdf724355102/regex-2026.5.9-cp313-cp313-win32.whl", hash = "sha256:f3844f134e834076677dd369976e9f5068679fcb8e50102fdf6b7ac96a3ec127", size = 266691, upload-time = "2026-05-09T23:13:29.549Z" }, + { url = "https://files.pythonhosted.org/packages/e6/1d/861a93719fb9ee7dbfc3761b3797b7a3e112a5d42c6129459d2d741be9b5/regex-2026.5.9-cp313-cp313-win_amd64.whl", hash = "sha256:3527bb4942d2c14552155406cdedd906567456821848aed1cb4933a391bf5eca", size = 277747, upload-time = "2026-05-09T23:13:31.859Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c6/0a2436ae4da1ba76e51cb98943c6838a9a721faa40ebe2dce07694ae34e3/regex-2026.5.9-cp313-cp313-win_arm64.whl", hash = "sha256:56a33f191f17d8c417f99945ebdc1e691d3af9605d86ec68c7e54a57e3e17af6", size = 270500, upload-time = "2026-05-09T23:13:33.525Z" }, + { 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/c4/43/fd1177a2032037c681baecdb3422ee4e1424aec4e4f470ef47793d325274/regex-2026.5.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:48036f6374aaa79eb3b754ec29c61d1c6b1606749d705a13f8854fa2539671f6", size = 293952, upload-time = "2026-05-09T23:13:38.307Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/04/99/eff29f1037dcab36702c9ee5d6858cf1ce2336ea8ea2987f64245b99ea5e/regex-2026.5.9-cp313-cp313t-win32.whl", hash = "sha256:ed2c9e8068b614c574d8d30e543d617cf5379b0535d46f97ef00e904745a08b5", size = 269951, upload-time = "2026-05-09T23:14:03.661Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9d/8870b8981d27b22cda77bb26a5ac7ebfa9c7d9e0dea195a834a82380e748/regex-2026.5.9-cp313-cp313t-win_amd64.whl", hash = "sha256:b46b0f094dc1d3b90356c85a0bd2c9bafc4a6a190b9d6f8ddd5a033b6e088ed4", size = 281240, upload-time = "2026-05-09T23:14:05.56Z" }, + { url = "https://files.pythonhosted.org/packages/72/b1/3379415e8f135c13ac551353397cc4fe97b4978f3cac73c5fcbcded548b8/regex-2026.5.9-cp313-cp313t-win_arm64.whl", hash = "sha256:872acc074bd29ffc9913ecdfedf6ea77502312ca44a4aa0d3779089c6069d8de", size = 272383, upload-time = "2026-05-09T23:14:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/13/3e/9c3cd292d8808b3645a2ce517e200179b6d0e903f176300bd8b542e14de5/regex-2026.5.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:1bd7587a2948b4085195d5a3374eaf4a425dc3e55784c038175355ecf3bbbf8a", size = 490376, upload-time = "2026-05-09T23:14:09.64Z" }, + { url = "https://files.pythonhosted.org/packages/60/70/d43ee8a2ca0a8b68d167f21658b85520ac0574617c7f320367c5047f7556/regex-2026.5.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dea2e88e1cce4522496cce630e11e67b98b7076620bc4336c3f674bc21a375f4", size = 291964, upload-time = "2026-05-09T23:14:11.424Z" }, + { url = "https://files.pythonhosted.org/packages/21/91/9d50b433828d8e74196904e168a43abf1e6e88b2a15d47ed742456720c37/regex-2026.5.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2099f7e7ff7b6aa3192312650a56e91cc091e49d50b04e4f6f8b6e28b3b27f1c", size = 289682, upload-time = "2026-05-09T23:14:13.123Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/b835e3cafbb9d977736912436259ff551d60919f7d7b3d37d46659c63564/regex-2026.5.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecd353045824e4477562a2ac718c25799cdaaa41f7aa925a806a8a3e6848a5b9", size = 796996, upload-time = "2026-05-09T23:14:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a6/9f992d00019166b9de01c546dd4549bc679f2a68df11b877740b0760b7c2/regex-2026.5.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65c8c8c37377794bd5b2f3ebe51919042bf17aec802e23c833d89782ed0c78af", size = 866089, upload-time = "2026-05-09T23:14:17.757Z" }, + { url = "https://files.pythonhosted.org/packages/e0/08/4d32af657e049b19cb62b02e46e38fe1518797bfb2203ee93a510b21b0dc/regex-2026.5.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b73ab8afcf66c622db143d1c6fda4e58e4d537ee4f125229ad47b1ab80f34c0", size = 911530, upload-time = "2026-05-09T23:14:20.353Z" }, + { url = "https://files.pythonhosted.org/packages/d9/27/2af43dd1dc201d1fecefda64a45f4ad0995855b92724f795a777b402ee69/regex-2026.5.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0de5cf193997384ed2ca6f1cd4f78055b255d93d82d5a8cd6ba0d11c10b167e4", size = 800643, upload-time = "2026-05-09T23:14:22.265Z" }, + { url = "https://files.pythonhosted.org/packages/a4/dd/23a249047013b5321d4a60c4d2437462086f601b061776a525e5fba2a59f/regex-2026.5.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d641a8c9a61618047796d572a39a79b26167b0411d2c3031937b2fe2d081e2cf", size = 777223, upload-time = "2026-05-09T23:14:24.179Z" }, + { url = "https://files.pythonhosted.org/packages/94/6a/e85ed9538cd19586d0465076a4578a12e093ce776d15f3f8ce92733a8dd6/regex-2026.5.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24b2355ef5cc9aa5b8f07d17704face1c166fdcc2290fa7bd6e6c925655a8346", size = 785760, upload-time = "2026-05-09T23:14:26.065Z" }, + { url = "https://files.pythonhosted.org/packages/2a/c4/f25473209438638e947c55f9156fd8f236f74169229028cc99116380868e/regex-2026.5.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a24852d3c29ad9e47593593d8a247c44ccc3d0548ef12c822d6ed0810affe676", size = 860891, upload-time = "2026-05-09T23:14:28.17Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f7/f4f86e3c74419c37370e91f150ae0c2ef7d34b2e0e4cdd5da046a02e4022/regex-2026.5.9-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:916714069da19329ef7de197dcbc77bb3104145c7c2c864dbfbe318f46b88b14", size = 765891, upload-time = "2026-05-09T23:14:30.06Z" }, + { url = "https://files.pythonhosted.org/packages/26/70/704d8e13765939146b1cd0ef4e2feb71d7929727d2290f026eed10095955/regex-2026.5.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fa411799ca8da32a8d38d020a88faa5b6f91657d284761352940ecf9f7c3bbdd", size = 851380, upload-time = "2026-05-09T23:14:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/26/29/1a13582a8460038edc38e49f64ceb0dd7c60f5caba77571f4bf6601965d9/regex-2026.5.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e6da47d679b7010ef27556b6e0f99771b744936db1792a10ceac6547ae1503e", size = 789350, upload-time = "2026-05-09T23:14:34.799Z" }, + { url = "https://files.pythonhosted.org/packages/73/56/3dcafe34fc72e271d62ad9a291801e88a1457bb251c132f15fcc2e5aad1a/regex-2026.5.9-cp314-cp314-win32.whl", hash = "sha256:98bd73080e8756255137e1bd3f3f00295bbc5aa383c0e0f973920e9134d7c4ad", size = 272130, upload-time = "2026-05-09T23:14:36.729Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9c/02eebf0be95efe416c664db7fb8b6b05b7a0b06a7544f2884f2558b0526f/regex-2026.5.9-cp314-cp314-win_amd64.whl", hash = "sha256:ff8d372ac2acdc048d1c19916f27ee61bc5722728458ba6ca5052f2c72d51763", size = 280999, upload-time = "2026-05-09T23:14:39.126Z" }, + { url = "https://files.pythonhosted.org/packages/70/5a/1dd1abee76cb7a846a0bcf42fdc87e5720c3c33c24f3e37814310a513d9f/regex-2026.5.9-cp314-cp314-win_arm64.whl", hash = "sha256:e1d93bf647916292e8edcec150c07ddf3dc50179ccaf770c04a7f9e452155372", size = 273500, upload-time = "2026-05-09T23:14:41.059Z" }, + { url = "https://files.pythonhosted.org/packages/86/c1/c5f619b0057a7965cb78ec559c1d7a45ce8c99a35bea95483d64959a93d9/regex-2026.5.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:83d0ee4a57d1c87cb549e195ec300b8f0ec3a82eba66d835e4e2ed8634fe4499", size = 494269, upload-time = "2026-05-09T23:14:42.869Z" }, + { url = "https://files.pythonhosted.org/packages/05/2c/5d01f1aee33de4bbe60c8452945bfc8477ca7c5ae4450f6bfe711036cb36/regex-2026.5.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d3d7eb5c9a7f6df82ed3cfac9beb93882a5cbcb5b8b157b56cb2b3b276574ac1", size = 293954, upload-time = "2026-05-09T23:14:44.822Z" }, + { url = "https://files.pythonhosted.org/packages/7a/fe/e8988b2ae2108c6ef71bd4aa8d87fbe257976dd0810e826cd75f701c68b6/regex-2026.5.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:075160bf16658e16d35233300b8453aac25de4cbea808d22348b6979668e924d", size = 292405, upload-time = "2026-05-09T23:14:47.211Z" }, + { url = "https://files.pythonhosted.org/packages/79/34/d2b0937faa7859263f7f0a3c6b103a1296306be6952dc173d0154e9a2f49/regex-2026.5.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45375819235558a4ff1c4971dc32881f022613abdb180128f5cb4768c1765a1c", size = 811855, upload-time = "2026-05-09T23:14:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/80/fe/daf53a47457a8486db66c66c01ceb9c2303eecee3f87197f1e77eb1a736d/regex-2026.5.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ead4b163ac30a29574510cd4b3e2e985ac5290c05fc7095557d6a5f403fc31b5", size = 871189, upload-time = "2026-05-09T23:14:51.555Z" }, + { url = "https://files.pythonhosted.org/packages/1c/75/058fc4470cbfbf57d800aff1a0022b929a3f9fa553ee10a0cdf2070eb31f/regex-2026.5.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c6e4218fbdfbcd4f6c19efca40930d24a621bf4b48cb76bc6640543bd28ef20", size = 917485, upload-time = "2026-05-09T23:14:53.633Z" }, + { url = "https://files.pythonhosted.org/packages/88/e7/179cfda3a28bc843b5c6cfe7f79f23489c791ed95f151083803660878432/regex-2026.5.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6351571c8a42b505eb555c0dc47d740d0fb66977dc142919eea6f4325b7c56a0", size = 816369, upload-time = "2026-05-09T23:14:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/41/90/6f0cc422071688266d344fca8462d787cba0a2c144acb25721f9a61ec265/regex-2026.5.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:002205cafd2a9e78c6290c7d1df277bf3277b3b7a30e0b4bb0dac2e2e3f7cb2d", size = 785869, upload-time = "2026-05-09T23:14:58.602Z" }, + { url = "https://files.pythonhosted.org/packages/02/67/a31f1760f09c27b251ef39e9beb541f462cf977381d067faa764c2c0e393/regex-2026.5.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8abd33fef90b2a9efac5557d6033ca82d1195ed3a15fea5af15ba7b463c6a63b", size = 801427, upload-time = "2026-05-09T23:15:00.642Z" }, + { url = "https://files.pythonhosted.org/packages/e3/c4/1a80654597b6bc1e1ea0494824c31200e8a956abe290afae9b19a166a148/regex-2026.5.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:31037c82eccb44b7ea2e9e221d7c01429430e989a1f4b91ea5a855f6017b509a", size = 866482, upload-time = "2026-05-09T23:15:03.384Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/960724e06482c08466ff5611e242e86f80062949cdf6b4b9cc317b9dd93d/regex-2026.5.9-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5604dfd046dc37eca90250fc3be938b076c8059fa772ac0ed6f499b0f0fb0415", size = 773022, upload-time = "2026-05-09T23:15:05.625Z" }, + { url = "https://files.pythonhosted.org/packages/50/a8/a9979c3e7918280e93159ebcab5ef1a65116dd4f3bd6091be0eae4a126e8/regex-2026.5.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e1b1b4e496afbb24f4a62aba855ee4f88f25578927697b340702e48c9ee6bc2", size = 856642, upload-time = "2026-05-09T23:15:07.966Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d4/a9b732f2f0072c0ab12227483abb24fffcb9f73f8a2b203df0a6d0434735/regex-2026.5.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:be3372b9df6ddecff6486d37e19095a7b4973137caf5512407a89f4455361f41", size = 803552, upload-time = "2026-05-09T23:15:10.215Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fe/1b3113817447a1d4155e4ac76d2e072f42c0bcba2f43fa8a0e756ea2cd91/regex-2026.5.9-cp314-cp314t-win32.whl", hash = "sha256:3ddd90103f9e5c471c49c7852ecc1fe27c7e45eb99e977aefe7caa4e779f4f58", size = 275746, upload-time = "2026-05-09T23:15:12.609Z" }, + { url = "https://files.pythonhosted.org/packages/92/73/93d42045302636c91f2e5ef588b65b84b01428f28ec77de256b1dfdfbe5c/regex-2026.5.9-cp314-cp314t-win_amd64.whl", hash = "sha256:ca518ed29c46eecba6010b15f1b9a479314d2de409536e71b6a13aa04e3b8a77", size = 285685, upload-time = "2026-05-09T23:15:15.086Z" }, + { url = "https://files.pythonhosted.org/packages/da/80/35b4c33c804a165a7f55289afda3ea9e3eb6d15800341a2d66455c0f1f30/regex-2026.5.9-cp314-cp314t-win_arm64.whl", hash = "sha256:5e41809d2683fcde7d5a8c87a6567ba1fb1ce0de9f31bff578de00a4b2d76daa", size = 275713, upload-time = "2026-05-09T23:15:16.98Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "rich" +version = "14.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/67/cae617f1351490c25a4b8ac3b8b63a4dda609295d8222bad12242dfdc629/rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9", size = 230524, upload-time = "2026-04-11T02:57:45.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "roman-numerals-py" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "roman-numerals" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", hash = "sha256:f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9", size = 4274, upload-time = "2025-12-17T18:25:41.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/2c/daca29684cbe9fd4bc711f8246da3c10adca1ccc4d24436b17572eb2590e/roman_numerals_py-4.1.0-py3-none-any.whl", hash = "sha256:553114c1167141c1283a51743759723ecd05604a1b6b507225e91dc1a6df0780", size = 4547, upload-time = "2025-12-17T18:25:40.136Z" }, +] + +[[package]] +name = "rpds-py" +version = "2026.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/a0/acf8b6fc20bfdcd3a45bd3f57680fb198e157b7e997b9123b10763798bd2/rpds_py-2026.5.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3397a5ed7174dc2786bb214030232fc36fe8e5584fec43a9952cc542b1a12036", size = 355609, upload-time = "2026-05-28T11:58:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/b6/95/f8203fd997484b1690a6869cd0e503b6c3c6be55b0ecc36d1a491fe742f0/rpds_py-2026.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:99ab6ba7bfa2cb0f96a04e3652355bf04e3f51aceb1e943b8541dab7ba4828cc", size = 348460, upload-time = "2026-05-28T11:58:52.374Z" }, + { url = "https://files.pythonhosted.org/packages/33/8c/b47326ad2f0be545a5e5c1a55937a12afaea7d392ba2837bb9680f57e6c9/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0efbe45632665e53e3db8fe1e5692db58fc5cb9bab4459d570b83efefe11164", size = 381031, upload-time = "2026-05-28T11:58:53.775Z" }, + { url = "https://files.pythonhosted.org/packages/22/0b/e83bbd97ffac6f6389b605cd4e1c8ac5761dc7e977769c9255d8c5adb7bd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:01d17b29c0c23d82b1f4751147ec49cf451f1fc2554eb9ef5f957e55d2656ead", size = 387121, upload-time = "2026-05-28T11:58:55.243Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0e/d285d1bc8864245919c61e1ca82263e4a66d337759c3a4cef72766ff9afc/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7559f72b94ae52659086c595dfa017cde03155f7832071d30959049052cb3ece", size = 501026, upload-time = "2026-05-28T11:58:56.788Z" }, + { url = "https://files.pythonhosted.org/packages/86/06/ccb2109a1e543437b5e43816f2b43b9554cc6783145528a4e3711e05c011/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e25b7088f9ccbfc0dfcaa52bf969300ca229e10ecf758974ebcbb080a4b37bb", size = 391865, upload-time = "2026-05-28T11:58:58.298Z" }, + { url = "https://files.pythonhosted.org/packages/3d/33/237173db1cfef10105b3839a24de00eb8d2a523711add4632447cdf0aedd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613fc4ee9eaef26dc5840666214dd6fbcebcf32f46e76f4abc473059f4e13dda", size = 378012, upload-time = "2026-05-28T11:58:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/97/64/1eae54e34d5161f9969295e80bd6b62a55f2b6ac5f2a5b60d02c2140e758/rpds_py-2026.5.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:85264a90ff4c05c1568dd65f5921c837614b67c60358fb4c17df3b7f2e90690a", size = 391111, upload-time = "2026-05-28T11:59:01.104Z" }, + { url = "https://files.pythonhosted.org/packages/d8/34/5bb334a5a0f65d77869217c4654f34c78a7d11b93938a3c076a2edeafc52/rpds_py-2026.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe71bca7d547acb17027c7fd1624ff8aae623499c498d3e7011182c4de5c25e0", size = 409225, upload-time = "2026-05-28T11:59:02.433Z" }, + { url = "https://files.pythonhosted.org/packages/16/0f/007ec21283b5b040b4ec3bd95e0402591e22bfa7d5c93dfe01c465c2d2d7/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05fa4f41f37ec97c9c260441a940450a192f78d774d2b097eee1379f1e1246a", size = 556487, upload-time = "2026-05-28T11:59:04.012Z" }, + { url = "https://files.pythonhosted.org/packages/ff/10/5437c94508169b6b22d8418fef7a66e9ffb5f3b9e9c94460f2eedafe06ff/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df1d2a1996755b24b9ecee92cb4d36c28f86f464a6a173349c26bab41e94b8c2", size = 620798, upload-time = "2026-05-28T11:59:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d5/9937dce4d6bda74157b954e7d1460db05a22f5929dccfeeba1ed27a93df0/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8895840ac4809e5f60c88fd07617cd71326e73d6e5a8aa783c5c0f7c24985de2", size = 584053, upload-time = "2026-05-28T11:59:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/6c/31/750617dd0ae1752471bf43f9e41d263398fae7cde7849d23b8574a70e617/rpds_py-2026.5.1-cp311-cp311-win32.whl", hash = "sha256:3684a59b158a7683aaeb8e25352e9a9dd2122cec78f2d8530266e4f91b4c7b3f", size = 214390, upload-time = "2026-05-28T11:59:08.402Z" }, + { url = "https://files.pythonhosted.org/packages/3c/bb/3dcab0e1d9516303f2eb672a5d6f62eca5a69e2886301e9c8c54b520c39b/rpds_py-2026.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:7bd530e6a530bb3ea892f194fafa455f3516ac25ecf7143fd33c09be62b0470a", size = 231097, upload-time = "2026-05-28T11:59:09.786Z" }, + { url = "https://files.pythonhosted.org/packages/49/d6/c6bbf5cb1cf12b9732df8074b57f6ef8341ba884c95d40632ae8bddb44e4/rpds_py-2026.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:0a5ae4dbe43c1076983b72616496919872ae7bbe7a1e21cc48336bc3154d130b", size = 226361, upload-time = "2026-05-28T11:59:11.079Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, + { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, + { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, + { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, + { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, + { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, + { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, + { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, + { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, + { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, + { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, + { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, + { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, + { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, + { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, + { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, + { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, + { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, + { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, + { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, + { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, + { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, + { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, + { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, + { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, + { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, + { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, + { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, + { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, + { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, + { url = "https://files.pythonhosted.org/packages/42/56/3fe0fb34820ff667be791b3a3c22b85e8bcba54e9c832f47438c191fa7be/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:edf2765d84e42447f112ad877af8fe1db0089aaec5b28e88d6eab45e7fe99cea", size = 357151, upload-time = "2026-05-28T12:01:53.43Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/3eb9ccdb9f143b8c9b003978898cb497f942a324c077401e6b8834238e63/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad3773236e95f7f33991eb125224b7da66f206504d032a253a02da7e134519fb", size = 350195, upload-time = "2026-05-28T12:01:54.901Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/dbda232bc4f3ed732120692ab0d2c8402cb020516556d8bee622dcef2413/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04df86b3f0fade39ec8fd0e0aab089b1da9fbd2b48df778a57ef96f5e7d38df", size = 381850, upload-time = "2026-05-28T12:01:56.601Z" }, + { url = "https://files.pythonhosted.org/packages/40/30/32e769839a358f78810c234f160f2cc21d1e4e47e1c0e0e0d535be5a0219/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6142dbd80c4df62a5d899f0d616d417f84e0bc8d32526c8e5589019d75d028a7", size = 387899, upload-time = "2026-05-28T12:01:58.212Z" }, + { url = "https://files.pythonhosted.org/packages/ab/86/ec84d243aadb3b34b71dd26a010d0930b2d284ff5fc9a69fec53810ee6fd/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b35217adefe87f2fe4db7e9766cabe84744bfe9616d9667be18988928c7f2dc", size = 501618, upload-time = "2026-05-28T12:01:59.888Z" }, + { url = "https://files.pythonhosted.org/packages/74/25/b60e52686bbff777a64f9e4f4d3dd57980dc846913777177a2c92e4937aa/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b95d5e11fc712b752081183a55a244c03cd00570489edd7014d8899f8ceb8162", size = 394003, upload-time = "2026-05-28T12:02:01.482Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c7/b3a6a588cc2219510ef3f42e207483a93950bedd1e3a0fd4015c95cff9e5/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141c9498daf2ace9eda35d2b0e376f9ea8b058d84f2aef4f96fccfd449a2f251", size = 379778, upload-time = "2026-05-28T12:02:03.197Z" }, + { url = "https://files.pythonhosted.org/packages/31/00/c7dba3fc8a3da8cb3f6db1eb3386be4d79c2e97c6890d20eb9ac66ae8c43/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:6f249f8b860a200ad35193af961183ebe9132710484e6f6ce0cf89fd83c63a9a", size = 392359, upload-time = "2026-05-28T12:02:04.817Z" }, + { url = "https://files.pythonhosted.org/packages/93/dd/472ba494c70753f93745992c99855bee0636daf74e6984e5e003f150316f/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4abbf391a70be864920858bf360f4fb380577c9a0f732438a1996726e2c195b", size = 412820, upload-time = "2026-05-28T12:02:06.401Z" }, + { url = "https://files.pythonhosted.org/packages/1d/6f/93831a3bfe789542ed0c1d0d74b78b440f055d6dc3ea4640eba2d95e6e23/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:c74005a7bb87752acf351c93897ec63ad77a07a0da7ecad9c050e32e7286ba34", size = 557243, upload-time = "2026-05-28T12:02:08.013Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ff/0b3d604614ffc77522c6b288fdbce68957eb583da1002aa65ba38ac0ee40/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:8213afbe8a3a906fb9acb2014423fe3359ee783d0bf90995f70623a3217bfa6c", size = 623541, upload-time = "2026-05-28T12:02:09.661Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ea/e7b0251441da9adfeaebcf29601d10f2a1455fcf0772fae9e7e19032bd96/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8c43a8a973270fd173bf48cdf80bbe66312421cba68d40845034f174f2389049", size = 586326, upload-time = "2026-05-28T12:02:11.47Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, + { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, + { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, + { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, + { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, + { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/94/dcdaeb1713cab9c84def276cfac7388b17c7d9855bbcfe88d77e4dbafd44/s3transfer-0.19.0.tar.gz", hash = "sha256:ce436931687addc4c1712d52d40b32f53e88315723f107ffa20ba82b05a0f685", size = 165171, upload-time = "2026-06-16T19:44:51.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/5f/4c174edad94f82de888ac00a5ddd8d07b35609b6c94f0bdf4d74af57703e/s3transfer-0.19.0-py3-none-any.whl", hash = "sha256:777cc2415536f1debadb5c2ef7779275d0fc0fe0e042411cdd6caebeb2685262", size = 90101, upload-time = "2026-06-16T19:44:50.439Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals-py" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, +] + +[[package]] +name = "sphinx-autodoc2" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/5f/5350046d1aa1a56b063ae08b9ad871025335c9d55fe2372896ea48711da9/sphinx_autodoc2-0.5.0.tar.gz", hash = "sha256:7d76044aa81d6af74447080182b6868c7eb066874edc835e8ddf810735b6565a", size = 115077, upload-time = "2023-11-27T07:27:51.407Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/e6/48d47961bbdae755ba9c17dfc65d89356312c67668dcb36c87cfadfa1964/sphinx_autodoc2-0.5.0-py3-none-any.whl", hash = "sha256:e867013b1512f9d6d7e6f6799f8b537d6884462acd118ef361f3f619a60b5c9e", size = 43385, upload-time = "2023-11-27T07:27:49.929Z" }, +] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736, upload-time = "2023-07-08T18:40:54.166Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496, upload-time = "2023-07-08T18:40:52.659Z" }, +] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload-time = "2023-04-14T08:10:22.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "sse-starlette" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/1b/bc9e3e7a72dcdad7dc7888758f5d00f56f8909ed5cfdff822bd72bb4c520/sse_starlette-3.4.5.tar.gz", hash = "sha256:83072538bc211a2f68b7b0422226c4af3e9b62e106e07034664b832ca019842a", size = 35249, upload-time = "2026-06-20T17:36:58.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/75/c88d3f5dafd59c791da1ce27650d30bf5b70cbf1cbf01cd00e5f9e360915/sse_starlette-3.4.5-py3-none-any.whl", hash = "sha256:e71bad53323f65573c3864a6c3bd0c1eb6e5f092b2e48082b0c35927d19ca296", size = 16518, upload-time = "2026-06-20T17:36:56.729Z" }, +] + +[[package]] +name = "starlette" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, +] + +[[package]] +name = "strands-agents" +version = "1.45.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, + { name = "botocore" }, + { name = "docstring-parser" }, + { name = "jsonschema" }, + { name = "mcp" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation-threading" }, + { name = "opentelemetry-sdk" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "typing-extensions" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/ca/42bedf73ca6d47939e20ad58e90f649e78d53a38f40b5d31ca8078458a61/strands_agents-1.45.0.tar.gz", hash = "sha256:c7e65978d319a5de74b52e7511099951f473c9ccc10368c1ebe13d87f8625d06", size = 1101805, upload-time = "2026-06-25T20:40:23.06Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/81/b189a0944d95465de4b97f8b3fb351df458cfe594572a6dfe7068363a515/strands_agents-1.45.0-py3-none-any.whl", hash = "sha256:9a38363a255f9fd89dbdbd766fdf240524ee4af403865412ed139dc904fbb5ad", size = 579326, upload-time = "2026-06-25T20:40:21.121Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + +[[package]] +name = "tiktoken" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/e5/5f3cb2159769d0f4324c0e9e87f9de3c4b1cd45848a96b2eb3566ad5ca77/tiktoken-0.13.0.tar.gz", hash = "sha256:c9435714c3a84c2319499de9a300c0e604449dd0799ff246458b3bb6a7f433c1", size = 38986, upload-time = "2026-05-15T04:51:27.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/4c/1bc81f4cd53e827c4ee67ca951b5935724716049452d8dfa09b8b82372bb/tiktoken-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7bfe1849caa65d1e1d9871817170ec497bbb7984e182012e1bdce72f66608cdb", size = 1036353, upload-time = "2026-05-15T04:50:21.757Z" }, + { url = "https://files.pythonhosted.org/packages/75/91/10b9c7076bc02c246c853201fdbbe300a4b8c5ed7b84c25f7403f4e32655/tiktoken-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:91c180fe255bd5a86d8316210d2833a1d4d33d026cd86a67812f4773743c8d26", size = 984644, upload-time = "2026-05-15T04:50:23.256Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e4/fceae98015fab47fcd49b8bd7f46145bcd187a47e0add1e5378ed67ef980/tiktoken-0.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:059c8ecf554eb5b41e6e054ba467b871b03277d267dee7244380aca4359747d4", size = 1119261, upload-time = "2026-05-15T04:50:24.348Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/fe42ad00de01a8c4a49ad8649a2c8a316835a9cad5961b11d21eac0020a5/tiktoken-0.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:36217497eaffc158607a3b26f065300db2aefd43b115263f3b9688ce38146173", size = 1138253, upload-time = "2026-05-15T04:50:25.505Z" }, + { url = "https://files.pythonhosted.org/packages/03/c4/ccee1ecccca107e9a16efcecdeeb964c325305038554d466ece65b42338f/tiktoken-0.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:303f7d91b4fce3baddbcde05c139091d4caa5026ac7214c1dc7ff7a71ee429ff", size = 1185747, upload-time = "2026-05-15T04:50:27.02Z" }, + { url = "https://files.pythonhosted.org/packages/9d/03/cd0cba295522b91eb55c6b2704f1df895f8226cfe60ab10d4d51d0cc9e69/tiktoken-0.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5d48843bee149630eb735a99e1f4a85b47308d21868ea63163f6e87768d3cfed", size = 1241265, upload-time = "2026-05-15T04:50:28.815Z" }, + { url = "https://files.pythonhosted.org/packages/7e/25/a10efd564402d82c2ff50d12057353ace447aa8007deceaa48641f63d35c/tiktoken-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:fc1c44cd37b43fc46bae593129164f4f281e82ea116b57a85aa81bda57eafc94", size = 876509, upload-time = "2026-05-15T04:50:30.026Z" }, + { url = "https://files.pythonhosted.org/packages/85/8e/144bde4e01df66b34bb865557c7cd754ed08b036217ebd79c9db5e9048a9/tiktoken-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32ac870a806cfb260a02d0cb70426aef02e038297f8ad50df5040bb5af360791", size = 1034888, upload-time = "2026-05-15T04:50:31.579Z" }, + { url = "https://files.pythonhosted.org/packages/36/18/d4ac9d20956cdebca04841316660ed584c2fecdc2b81722a28bc7ad3b1e4/tiktoken-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d9980f11429ed2d737c463bb1fb78cf330caa026adf002f714aced7849a687b", size = 982970, upload-time = "2026-05-15T04:50:32.961Z" }, + { url = "https://files.pythonhosted.org/packages/74/ed/6bb8d05b9f731f749fee5c6f5ca63e981143c826a5985877330507bd13b7/tiktoken-0.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3f277ebea5edd7b8bf03c6f9431e1d67d517530115572b2dc1d465326e8f88c7", size = 1115741, upload-time = "2026-05-15T04:50:34.475Z" }, + { url = "https://files.pythonhosted.org/packages/34/de/2ca96b07a82d972b74fe4b46de055b79c904e45c7eab699354a0bfa697dc/tiktoken-0.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:a116178fa7e1b4065bff05214360373a65cac22f965be7b3f73d00a0dbfe7649", size = 1136523, upload-time = "2026-05-15T04:50:35.782Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/9dafec002c2d4424378563cf4cf5c7fb93631d2a55013c8b87554ee4012c/tiktoken-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c397ddda233208345b01bd30f2fca79ff730e55731d0108a603f9bc57f6af3b", size = 1181954, upload-time = "2026-05-15T04:50:36.99Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d0/1f8578c45b2f24759b46f0b50d31878c63c73e6bf0f2227e10ec5c5408dc/tiktoken-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95097e4f89b06403976e498abf61a0ee73a7497e73fb599cb211d8197a054d91", size = 1240069, upload-time = "2026-05-15T04:50:38.221Z" }, + { url = "https://files.pythonhosted.org/packages/aa/90/28d7f154888610aa9237e541986beb62b479df29d193a5a0617dbb1514d0/tiktoken-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:8f2d16e7a7c783ad81f36e457d046d1f1c8af70b22aec8a13238efe531977c41", size = 874748, upload-time = "2026-05-15T04:50:39.587Z" }, + { url = "https://files.pythonhosted.org/packages/9c/83/b096c859c2a47c11731bf2f5885f4028b809dfe2396582883eed9cae372f/tiktoken-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5df5d1507bd245f1ccad4a074698240021239e455eb0bb4ced4e3d7181872154", size = 1034228, upload-time = "2026-05-15T04:50:40.988Z" }, + { url = "https://files.pythonhosted.org/packages/53/61/c68e123b6d753e3fc2751e9b18e732c9d8bf1e1926762e736eee935d931c/tiktoken-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fe806a50664e83a6ffd56cbd1e4f5dcc6cd32a3e7538f70dc38b1a271384545", size = 982978, upload-time = "2026-05-15T04:50:42.195Z" }, + { url = "https://files.pythonhosted.org/packages/ef/8b/96cc178cc584e65d363134500f297790b06cd48cdeb1e8fcf7bbe60f4715/tiktoken-0.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:125bc05005e747f993a83dc67934249932d6e4209854452cd4c0b1d53fba3ba2", size = 1116355, upload-time = "2026-05-15T04:50:43.564Z" }, + { url = "https://files.pythonhosted.org/packages/86/f5/bab735d2c72ea55404b295d02d092644eb5f7cc6205e34d35eb9abfb9ab2/tiktoken-0.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5e6358911cab4adee6712da27d65573496a4f68cf8a2b5fca6a4ad10fc5748cf", size = 1135772, upload-time = "2026-05-15T04:50:44.782Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b9/6de04ebdf904edfaad87788011b3735087a0c9ea671b9027e1e4e965e8c8/tiktoken-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:975cbd78d085d75d26b59660e262736dcaed1e35f8f142cd6291025c01d25486", size = 1182415, upload-time = "2026-05-15T04:50:46.422Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9c/470a05f3b1caf038f44880e334d47ab674e0c80d514c66b375d14d5afa10/tiktoken-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ab9bc99fa020a4c283424590ecd7f3afd70c1c281cb3fa3192a6c3af9f9615", size = 1239879, upload-time = "2026-05-15T04:50:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/42/a6/c1936d16055436cb32e6c6128d68629622e00f4768562f55653752d34768/tiktoken-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:6b1615f0ff71953d19729ceb18865429c185b0a23c5353f1bbca34a394bf60f7", size = 874829, upload-time = "2026-05-15T04:50:49.202Z" }, + { url = "https://files.pythonhosted.org/packages/d6/07/acb5992c3772b5a36284f742cfb7a5895aa4471d1848ac31464ad50d7fdf/tiktoken-0.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6eb4a5bfbc6426938026b1a334e898ac53541360d62d8c689870160cc80abd67", size = 1033600, upload-time = "2026-05-15T04:50:50.4Z" }, + { url = "https://files.pythonhosted.org/packages/14/e9/742e9aec30f59b9f161f7ff7cd072e02ea836c9e1c0854a8076dfcd40d5c/tiktoken-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:43cee3e5400573b2046fbf092cc7a5bc30164f9e4c95ce20714da929df48737a", size = 982516, upload-time = "2026-05-15T04:50:52.03Z" }, + { url = "https://files.pythonhosted.org/packages/72/74/ca1541b053e7648254d2e4b42a253e1bb4359f2c91a0a8d49228c794e1a0/tiktoken-0.13.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:7de52e3f566d19b3b11bd37eea552c6c305ad74081f736882bd44d148ed4c48d", size = 1115518, upload-time = "2026-05-15T04:50:53.543Z" }, + { url = "https://files.pythonhosted.org/packages/46/e3/93825eaf5a4a504795b787e5d5dea07fbeb3dabf97aa7b450be8bde59c89/tiktoken-0.13.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:51384448aa508e4df84c0f7c1dc3211c7f7b8096325660ee5fc82f3e11b381ce", size = 1136867, upload-time = "2026-05-15T04:50:55.191Z" }, + { url = "https://files.pythonhosted.org/packages/8c/46/002b68de6827091d5ae90b048f326e8aad8d953520950e5ce1508879414f/tiktoken-0.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e28157350f7ebf35008dd8e9e0fdb621f976e4230c881099c85e8cf07eaa50e2", size = 1181826, upload-time = "2026-05-15T04:50:56.296Z" }, + { url = "https://files.pythonhosted.org/packages/db/c6/d393e3185a276505182f7abd93fe714f3c444a2be9180798fa052347504e/tiktoken-0.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:165cf1820ea4a354985c2490a5205d4cc74661c934aca79dd0368232fff94e0f", size = 1239489, upload-time = "2026-05-15T04:50:57.918Z" }, + { url = "https://files.pythonhosted.org/packages/b7/4d/bc07d1f1635d4897a202acc0ae11c2886eaa7325c359ba4741b47bf8e225/tiktoken-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6c43a675ca14f6f2749ba7f12075d37456015a24b859f2517b9beb4ef30807ec", size = 873820, upload-time = "2026-05-15T04:50:59.528Z" }, + { url = "https://files.pythonhosted.org/packages/8c/93/0dd6adca026a616c3a92974566b43381eea4b475ce1f36c062b8271a9ac5/tiktoken-0.13.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaaaef47c2406277181d2086484c317bf7fc433e2d5d03ff94f56b0dcec87471", size = 1034977, upload-time = "2026-05-15T04:51:00.957Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5ec6e6bc5b30bed6d93f7f2162d8f6b32437b3ba27cb527cfe004f6109c9/tiktoken-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ca8b310bd93b3772cb1b7922d915446864860f562bdfe4825c63a0aed3fb28cd", size = 983635, upload-time = "2026-05-15T04:51:02.629Z" }, + { url = "https://files.pythonhosted.org/packages/94/b0/c8ae9aff00d625c50659b4513e707a0462c4bf5d4d6cc1b802103225c02e/tiktoken-0.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:32e0c12305105002c047b3bb1070b0dd9a73b0cb3b2856a8972b810e7a4f5881", size = 1116036, upload-time = "2026-05-15T04:51:04.082Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ac/6a5dddd1d0a6018ecb389bd0353e6b4a515eb4d2286611bd0ace1937b9e1/tiktoken-0.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:5ba5fd62507a932d1241346179e3b39bc7bf7408f03c272652d93b3bedf5db24", size = 1135544, upload-time = "2026-05-15T04:51:05.229Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b8/585032b4384b2f7dcdaddcb52865c83a701a420d09e3c2b4a2be1c450c57/tiktoken-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d108bc2d470fc53c8ecd24f2c0fd2b5f98c33e87cdb6aa2e9b8c5dced703d273", size = 1182217, upload-time = "2026-05-15T04:51:06.517Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b6/993ff1ded3958215fd341a847b8e5ffeb5de473f435296870d314fc91ac4/tiktoken-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb99cb5127449f58d0a2d5f5ccfb390d8dbdfd919c221246caaee29d8725ed51", size = 1239404, upload-time = "2026-05-15T04:51:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3d/fef7e06e3b33e7538db0ced734cf9fe23b6832d2ac4990c119c377aec55e/tiktoken-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:115c4f26ffa11caac8b54eea35c2ad38c612c20a48d35dd15d70a02ac6f51f58", size = 918686, upload-time = "2026-05-15T04:51:08.925Z" }, + { url = "https://files.pythonhosted.org/packages/c1/82/a7fc44582bc32ab00de988a2299bf77c077f59068b233109e34b7d6ca7e6/tiktoken-0.13.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:472527e9132952f2fbf77cd290658bacf003d4d5a3fabc18e5fbd407cbae4d9b", size = 1034454, upload-time = "2026-05-15T04:51:10.035Z" }, + { url = "https://files.pythonhosted.org/packages/37/d0/24d8a890c14f432a05cea669c17bebeaa99f96a7c79523b590f564246411/tiktoken-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e2f67d27c9626cdd25fe33d9313c5cdb3d8d82da646b68d6eb8e7e9c20e6448", size = 982976, upload-time = "2026-05-15T04:51:11.23Z" }, + { url = "https://files.pythonhosted.org/packages/49/b7/2ab43f62788a9266187a9bfc1d3af99ad83e5eaa25fbef168a69cd5ad14f/tiktoken-0.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2b920b35805cd64585a37c3dc7ce65fba4d2d36016be01e1d7942482ca29093a", size = 1115526, upload-time = "2026-05-15T04:51:12.608Z" }, + { url = "https://files.pythonhosted.org/packages/64/39/1494321ed323ce7a14d88e3cd6cb9058625977df1c6961ddc492bd10a9f3/tiktoken-0.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:493af3aa28a4aaf2e3d2600a2ee717252c9bf5ab38fff94eb5a02db5ab77e5ad", size = 1136466, upload-time = "2026-05-15T04:51:13.926Z" }, + { url = "https://files.pythonhosted.org/packages/96/d9/dfd086aa2d918c563a140720e0ce296cada1634efd2783d5cf51e05f984e/tiktoken-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6644c9c2b5cf3916f5a3641d7d12fdb3f006a7b3d9ff6acdaec44e29ab1ff91e", size = 1181863, upload-time = "2026-05-15T04:51:15.025Z" }, + { url = "https://files.pythonhosted.org/packages/2f/68/a18b4f307086954fdae32714cb4f85562e34f9d34ab206e61f1816aa6018/tiktoken-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cb65b60b9408563676d874a3a4ee573370066f0dc4e29d84e82e989c6517424", size = 1239218, upload-time = "2026-05-15T04:51:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/16/5b/f2aa703a4fc5d2dff73460a7d46cc2f3f44aa0f3dd8eeb20d2a0ecf68862/tiktoken-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:85b78cc3a2c3d48723ca751fa981f1fedccd54194ca0471b957364353a898b07", size = 918110, upload-time = "2026-05-15T04:51:17.237Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/60/21f715d9faba5f5407ff759472ade058ec4a507ad62bcea47cb847239a73/tokenizers-0.23.1.tar.gz", hash = "sha256:1feeeadf865a7915adc25445dea30e9933e593c31bb96c277cee36de227c8bfa", size = 365748, upload-time = "2026-04-27T14:43:25.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/39/b87a87d5bb9470610b80a2d31df42fcffeaf35118b8b97952b2aff598cc7/tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e03d6ffcbe0d56ee9c1ccd070e70a13fa750727c0277e138152acbc0252c2224", size = 3146732, upload-time = "2026-04-27T14:43:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6a/068ed9f6e444c9d7e9d55ce134181325700f3d7f30410721bdc8f848d727/tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0948bbb1ac1d7cdfc9fb6d62c596e3b7550036ad60ecd654a66ad273326324e", size = 3054954, upload-time = "2026-04-27T14:43:13.745Z" }, + { url = "https://files.pythonhosted.org/packages/6c/36/e006edf031154cba92b8416057d92c3abe3635e4c4b0aa0b5b9bb39dde70/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf13402aff9bc533c89cb849ec3b412dc3fbeacc9744840e423d7bf3f7dc0e3", size = 3374081, upload-time = "2026-04-27T14:43:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ef/7735d226f9c7f874a6bee5e3f27fb25ecabdf207d37b8cf45286d0795893/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f836ca703b89ae07919a309f9651f7a88fd5a33d5f718ba5ad0870ec0256bad6", size = 3247641, upload-time = "2026-04-27T14:43:03.856Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d9/24827036f6e21297bfffda0768e58eb6096a4f411e932964a01707857931/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae848657742035523fdf261773630cb819a26995fcd3d9ecae0c1daf6e5a4959", size = 3585624, upload-time = "2026-04-27T14:43:10.664Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/22f3582b3a4f49358293a5206e25317621ee4526bfe9cdaa0f07a12e770e/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b09e85775d5187941e7bab30e941b4134ab4a7dd8c68e783d231fb7ca27c51", size = 3844062, upload-time = "2026-04-27T14:43:05.643Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/b8f8814eef95800f20721384136d9a1d22241d50b2874357cb70542c392f/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea5a0ce170074329faaa8ea3f6400ecde604b6678192688533af80980daae71a", size = 3460098, upload-time = "2026-04-27T14:43:08.854Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d5/1353e5f677ec27c2494fb6a6725e82d56c985f53e90ec511369e7e4f02c6/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b405006415ea148a992d093699c66eb01952bf59f4d5727089a98bda45a4", size = 3346235, upload-time = "2026-04-27T14:43:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/71/89/39b6b8fc073fb6d413d0147aa333dc7eff7be65639ac9d19930a0b21bf33/tokenizers-0.23.1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:56f3a77de629917652f876294dc9fe6bad4a0c43bc229dc72e59bb23a0f4729a", size = 3426398, upload-time = "2026-04-27T14:43:07.264Z" }, + { url = "https://files.pythonhosted.org/packages/0f/80/127c854da64827e5b79264ce524993a90dddcb320e5cd42412c5c02f9e8a/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d10a6d957ef01896dc274e890eee27d41bd0e74ef31e60616f0fc311345184e", size = 9823279, upload-time = "2026-04-27T14:43:17.222Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ba/44c2502feb1a058f096ddfb4e0996ef3225a01a388e1a9b094e91689fe93/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1974288a609c343774f1b897c8b482c791ab17b75ab5c8c2b1737565c1d82288", size = 9644986, upload-time = "2026-04-27T14:43:19.45Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c1/464019a9fb059870bfe4eebb4ba12208f3042035e258bf5e782906bd3847/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:120468fb4c24faf0543c835a4fabafa4deb3f20a035c9b6e83d0b553a97615d4", size = 9976181, upload-time = "2026-04-27T14:43:21.463Z" }, + { url = "https://files.pythonhosted.org/packages/79/94/3ac1432bda31626071e9b6a12709b97ae05131c804b94c8f3ac622c5da32/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e3d8f40ea6268047de7046906326abed5134f27d4e8447b23763afe5808c8a96", size = 10113853, upload-time = "2026-04-27T14:43:23.617Z" }, + { url = "https://files.pythonhosted.org/packages/6a/dd/631b21433c771b1382535326f0eca80b9c9cee2e64961dd993bc9ac4669e/tokenizers-0.23.1-cp310-abi3-win32.whl", hash = "sha256:93120a930b919416da7cd10a2f606ac9919cc69cacae7980fa2140e277660948", size = 2536263, upload-time = "2026-04-27T14:43:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/2553f72aaf65a2797d4229e37fa7fbe38ffbf3e32912d31bdd78b3323e59/tokenizers-0.23.1-cp310-abi3-win_amd64.whl", hash = "sha256:e7bfaf995c1bdbbd21d13539decb6650967013759318627d85daeb7881af16b7", size = 2798223, upload-time = "2026-04-27T14:43:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2b/2be299bab55fc595e3d38567edb1a87f86e594842968fa9515a07bdcf422/tokenizers-0.23.1-cp310-abi3-win_arm64.whl", hash = "sha256:a26197957d8e4425dfba746315f3c425ea00cfa8367c5fbc4ec73447893dcea9", size = 2664127, upload-time = "2026-04-27T14:43:26.949Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + +[[package]] +name = "tqdm" +version = "4.68.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/d7/0535a28b1f5f24f6612fb3ff1e89fb1a8d160fee0f976e0aa6803862134b/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", size = 170596, upload-time = "2026-06-17T07:36:52.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" }, +] + +[[package]] +name = "typer" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20260518" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/83/4a1afc3fbfcf5b8d46fc390cd95ed6b0dc9010a265f4e9f46314efffa37a/types_pyyaml-6.0.12.20260518.tar.gz", hash = "sha256:d917f83fb38462550338c1297faedd860b3ec83912b96b1e3d73255f7473e466", size = 17850, upload-time = "2026-05-18T06:01:58.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/a2/c01db32be2ae7d6a1689972f3c492b149ee4e164b12fdfd9f64b50888215/types_pyyaml-6.0.12.20260518-py3-none-any.whl", hash = "sha256:d2150f75a231c9fe9c7463bd29487d93e60bac90400287351384bc2284eba7cd", size = 20312, upload-time = "2026-05-18T06:01:57.368Z" }, +] + +[[package]] +name = "types-requests" +version = "2.33.0.20260518" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/01/c5a19253fe1ac159159ddf9a3a07cec8bb5e486ec4d9002ad2821da0e5d2/types_requests-2.33.0.20260518.tar.gz", hash = "sha256:df7bd3bfe0ca8402dfb841e7d9be714bb5578203283d66d7dc4ef69343449a5e", size = 24752, upload-time = "2026-05-18T06:07:37.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/bc/b139710a3b6018f7fb2b9508b35c8af564e61bf2bf4fa619d088f3e16f85/types_requests-2.33.0.20260518-py3-none-any.whl", hash = "sha256:626d697d1adaaff76e2044dc8c5c051d8f21abc157bdfe204a75558076fe0bf0", size = 21391, upload-time = "2026-05-18T06:07:37.044Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "uuid-utils" +version = "0.16.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/5a/5da7ae85b38e3eddba0be3e8e4328f90882fe92989728e6fb552963d4c42/uuid_utils-0.16.2.tar.gz", hash = "sha256:fa637e4f314ad5b59ff6d8e809d506443d68bef30bfaecdfcfe02cce689abb2f", size = 42962, upload-time = "2026-06-18T13:36:48.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/96/4023966d42fd9bbf9e2a8ce2b25930113688128b569f68bc4697cb18181d/uuid_utils-0.16.2-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:fadd23eee409237fb8637a35796a6e108873c28b40f7de89a36685f18ca055ad", size = 567776, upload-time = "2026-06-18T13:35:02.902Z" }, + { url = "https://files.pythonhosted.org/packages/fc/30/764d2a76e8e7688abd5577e6024787c13692095eb1230fd1936f27205cd9/uuid_utils-0.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:79c5a3bd4301257b9a524efd16baf61ea65cd0d1b60b47d80f20b151fd65a09f", size = 288938, upload-time = "2026-06-18T13:35:04.285Z" }, + { url = "https://files.pythonhosted.org/packages/45/ef/58077250fe04eda4a3f9fba8c35be8d0937b7d3e02302ac1a6d942b77dae/uuid_utils-0.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90903ab7fcdfb0300390c15f5a68cb91f15139d9a1a93f134c783d7a973fa269", size = 327387, upload-time = "2026-06-18T13:35:05.406Z" }, + { url = "https://files.pythonhosted.org/packages/c6/fd/a9172970fa07ce0b9148ccc679a99540375c7bb32f8fbd72cf1e6cca43ef/uuid_utils-0.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f7a44f8250ec178c0af703c3f1b6e81865a771272ae735ca403f27c95c62f132", size = 334212, upload-time = "2026-06-18T13:35:06.611Z" }, + { url = "https://files.pythonhosted.org/packages/ca/58/d8fb393b44ad0b719d96a5b7809d0ee727f7e266d9e88a4da235cbfbf9f8/uuid_utils-0.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e97ab941660f781a8e45f15aba9ee01b40dbb96adb5c43617c1671a4604b25b", size = 450379, upload-time = "2026-06-18T13:35:07.97Z" }, + { url = "https://files.pythonhosted.org/packages/00/70/b3cf708e8942e6494742404a66f1586195a20c8fd235bdc79f385db383f1/uuid_utils-0.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a30b6a5790acb854e4b65fae7875e5d3c6f8076fa9c91dac43ff9e28380bc52", size = 327231, upload-time = "2026-06-18T13:35:09.327Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9c/4c5b16e752a2402259a3a9d1371227025e5b85182024c82a446cbe3ed6ea/uuid_utils-0.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5dfc3e9e75139a84898771d31958ece6cdee8e8f127700aa8aa26a4f1a348d57", size = 353455, upload-time = "2026-06-18T13:35:10.67Z" }, + { url = "https://files.pythonhosted.org/packages/0c/0f/3b14c47fab1544bcfb92d28bc468160a4fc6ff342d0e6defa8ff40d5e4bd/uuid_utils-0.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0529b1ef0788f663e1211d221b59a38ec67f9b084f1ea5342ba84358b3d87e98", size = 504028, upload-time = "2026-06-18T13:35:12.006Z" }, + { url = "https://files.pythonhosted.org/packages/39/79/1a133214626eb0e18c51ef196946b1263d65b578ffee432ad1b7afffa5d3/uuid_utils-0.16.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:cae08df8695f4b01fce2a8ab50e9e310971276d85dfc7103e977bed52d365094", size = 609803, upload-time = "2026-06-18T13:35:13.286Z" }, + { url = "https://files.pythonhosted.org/packages/87/49/22bff932af63764b4cad9c01299ed64c60d101962988efc13964b4165345/uuid_utils-0.16.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f69658c42411540cf58be958a47e317fd2302cc0b613ea5cff1e60d87be2846d", size = 569512, upload-time = "2026-06-18T13:35:14.661Z" }, + { url = "https://files.pythonhosted.org/packages/3f/98/371cc1f332f7463b9cfac0a66f984af00f4e3ada4a196b20879e35404e8b/uuid_utils-0.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:503f020acc7dbeb39c47fa33cf2971cf5960fa11f8394513fac461762a90c556", size = 532855, upload-time = "2026-06-18T13:35:15.99Z" }, + { url = "https://files.pythonhosted.org/packages/f1/06/f7ef7f6dabf68021eb6e961c09d16d517ed7587cedfff18969ba7f61798c/uuid_utils-0.16.2-cp311-cp311-win32.whl", hash = "sha256:aab7cdf28a3e2859ca4f40a3e3bf53eb35895039c80d4d8d8c5e15b90346c55c", size = 169971, upload-time = "2026-06-18T13:35:17.294Z" }, + { url = "https://files.pythonhosted.org/packages/75/8b/1e4b51c075eaadd23828b708249374db0bc40146f7b673027942d3383f45/uuid_utils-0.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:71192a59d473f3f638e2a238905046e2942006ad90ac5ec10d578e58ff9a08ce", size = 176464, upload-time = "2026-06-18T13:35:18.459Z" }, + { url = "https://files.pythonhosted.org/packages/d4/71/18a43b6e632adf3cb3cf5db777ea03f9d3b2b259de65de5e41419004c2a1/uuid_utils-0.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:ea175649789f1e93edbf1a0440cab18c9838977703917221777691d8d988d7bf", size = 176056, upload-time = "2026-06-18T13:35:19.826Z" }, + { url = "https://files.pythonhosted.org/packages/fd/07/294b72a572218bf6e92355203b832b3356c58a7e1e0b92a034497d15bef9/uuid_utils-0.16.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6f064dc54c6abecb09eb104d953bfb079f3c395e0d6b18899979f852d1083549", size = 560726, upload-time = "2026-06-18T13:35:21.053Z" }, + { url = "https://files.pythonhosted.org/packages/e5/3c/1095b6ab574a7fa69136d47bab5a43f320a8f00a0ecb96059fd49b1747b2/uuid_utils-0.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:dd7aa18db5cc826d482d876a826fee445839701f81f78567e7c74b4458d57a84", size = 288065, upload-time = "2026-06-18T13:35:22.547Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9d/6404d48fe71def0733c9568d96043b2e1945e2e4205c4eb525db3da42ba3/uuid_utils-0.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc25ad320c9b44c2d3ed33aff4f85b0b277bef4ff79b12c01ee58b52ea44be1d", size = 322946, upload-time = "2026-06-18T13:35:23.648Z" }, + { url = "https://files.pythonhosted.org/packages/74/00/8a009762015a134aa04b5451400e0ec9832ccd598ed4845f9aecb0be6299/uuid_utils-0.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d0ca752d51d1004caff65fccffd44b32a26cb099b546e0512cfa09facb683d6c", size = 330186, upload-time = "2026-06-18T13:35:24.757Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b0/1613bb98ac11234145aa5bc1de618be536818fef05dec595efb3e2b37097/uuid_utils-0.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8323136bb02355c1b973492ab98b0722206dfdedfb148e4115c35fcdf3889bad", size = 444583, upload-time = "2026-06-18T13:35:25.999Z" }, + { url = "https://files.pythonhosted.org/packages/93/66/83e62c7a152bbbb8b30ac58eaad81f3860ba2fba91a334c50f223f9ce878/uuid_utils-0.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bf8bfdffb22f620635580b17fd178272f30a9841b824b19b935c8db64bf09b6", size = 323064, upload-time = "2026-06-18T13:35:27.356Z" }, + { url = "https://files.pythonhosted.org/packages/15/37/c1b2faaf3a9d7952f321a9fee3ad74e05b25878bd9b7cd6b0398fe77f279/uuid_utils-0.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:61454f2139424a6cff14eca7849c28b3350f261453b74075aa20fe99592dbb16", size = 347967, upload-time = "2026-06-18T13:35:28.538Z" }, + { url = "https://files.pythonhosted.org/packages/24/d8/cdf79b242e41ae47b7cd617ac5d48f15ce44e81da8000379c757091ae5f8/uuid_utils-0.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:725110434a1d482a639a9ac467a24f1cb531d84ab52e454a13fe145b10b42cae", size = 499187, upload-time = "2026-06-18T13:35:30.042Z" }, + { url = "https://files.pythonhosted.org/packages/be/10/978d5ad82bc0fe7ff02d5be6f1eb83b090849f0a95bf8438593565273b7a/uuid_utils-0.16.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8197870739a3094990743a80f075fa0b17beafd6c187e5f360e021d90a12a6d1", size = 605696, upload-time = "2026-06-18T13:35:31.289Z" }, + { url = "https://files.pythonhosted.org/packages/3a/28/e382ee44a592e35b80397b493bf3fbbdb8e30a64eaaefc7dabc246aeb253/uuid_utils-0.16.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e10a02b3a31ed44c7c9a96abde335f5fa222735e73f3081d693414377eb3b016", size = 564975, upload-time = "2026-06-18T13:35:32.419Z" }, + { url = "https://files.pythonhosted.org/packages/a3/d0/f6011dbe4e5d751a8494715e014019cb5b242d8cd6dbec1cfec3d3fb2e81/uuid_utils-0.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd32dbca0792b9683160151dc07fad11b915020eed7c82b43faf0862c2ff06a0", size = 528462, upload-time = "2026-06-18T13:35:33.685Z" }, + { url = "https://files.pythonhosted.org/packages/42/7f/279e6159c37f43feb9dd70218b49a26696cefddaef1db7f4b79895eaf5d5/uuid_utils-0.16.2-cp312-cp312-win32.whl", hash = "sha256:dcdfcab60562d12dd43c1a6f495b1d089e41f0e10fac37d94db285d72b678c23", size = 167047, upload-time = "2026-06-18T13:35:34.862Z" }, + { url = "https://files.pythonhosted.org/packages/47/38/f72f7bed062601448ec2db47351e6c1faccd78fd693bbc6e067299d1fa11/uuid_utils-0.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:97ee6f5e803ea571f5f6da42efc97d8c5a13f121043680177f8470529b94e855", size = 173821, upload-time = "2026-06-18T13:35:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/37/61/8a025284a31c85b7c0c5319e96868c2c09dea3fc5f676c979a4cd4baf2e7/uuid_utils-0.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:72cfd9ff1e8a7c371a044687e77eb873721c4a9f4814e453439bfba595b84303", size = 172206, upload-time = "2026-06-18T13:35:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/3b48859953ee74fc26628ca5d9e5f848209655a0a8c934032fc596035976/uuid_utils-0.16.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c19b7d595d12923da682ed13d313c2333b9ebf214e65a47a24927a8a3a81b191", size = 560753, upload-time = "2026-06-18T13:35:38.531Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1c/77635489de5454f2a25411030f78d31931dbdc0c86114da00adb9b91f120/uuid_utils-0.16.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:909e26fa2451c8db31b9ed1d3c8e4ecf513b6d1619db4205997fe99eb6b4ef4f", size = 288056, upload-time = "2026-06-18T13:35:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0e/8e799537ea458abaefb0f5c3b3b05304d3faf413feb0997605a3f8ae2484/uuid_utils-0.16.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27271b37fbc6812bb1542c4b8e22ee00223a6bf7f62b1f38d3bcf8e92f6d9acd", size = 323196, upload-time = "2026-06-18T13:35:41.534Z" }, + { url = "https://files.pythonhosted.org/packages/e8/92/4e5b412d4710617fb83ed77b361f5fa6247b99bde2fa6ee07ddf851b59d1/uuid_utils-0.16.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dc4b9d96a2c689d664cf3fc7f7db46b82d2821fb2ce8a4f0798fc0a92c1569f8", size = 330858, upload-time = "2026-06-18T13:35:42.709Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e3/8173202b7cfcfeb4a588c5f8b85d3e2b44973384eb33167ee25c5c78867f/uuid_utils-0.16.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3bf41b696b0fe808df1b4091c70273a52ea033b0fe97341cd67ecd76d22bb3a", size = 444813, upload-time = "2026-06-18T13:35:43.917Z" }, + { url = "https://files.pythonhosted.org/packages/37/0d/c3918356932ce467b11e954d0c93697fb4652cf664957e3d9521f7ece22f/uuid_utils-0.16.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcc329be41bb6534ecb03e50596179ab76c7643ced33d13c66967d5ae1869663", size = 322828, upload-time = "2026-06-18T13:35:45.134Z" }, + { url = "https://files.pythonhosted.org/packages/f0/80/4020556682441b62a25b7d07798812115fca97d417a3498d5af6dce36504/uuid_utils-0.16.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4125bf6ed3ae443c05e140f8585d174b9d647295b12034d5ec94ae2ae38edefa", size = 347909, upload-time = "2026-06-18T13:35:46.364Z" }, + { url = "https://files.pythonhosted.org/packages/48/2f/a1e87e268df98f6740af81abf225532c173a971c64df0258c84b630e35a7/uuid_utils-0.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:840b21e609a9b203eee06bdc73e18397154447a9814a8e78d9b68e5104d9802f", size = 499469, upload-time = "2026-06-18T13:35:47.584Z" }, + { url = "https://files.pythonhosted.org/packages/25/75/5a1f297a09556c27d9617c44ab0510de5f3a70120df236f66b9d0fdd1976/uuid_utils-0.16.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5119bec75f56bd028d97472f72b1ed723a0d60b09a48017dc70a3cb1892ed081", size = 606160, upload-time = "2026-06-18T13:35:48.963Z" }, + { url = "https://files.pythonhosted.org/packages/7c/de/140f1d2a161320d1ac9073a03b9eb31fe35ae70f56f8971ec1fb45c14a44/uuid_utils-0.16.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9fe600ab7d3d4eb56986e814042c917e728ac92cd8a41f099a6b59b84d8bf9e6", size = 564856, upload-time = "2026-06-18T13:35:50.244Z" }, + { url = "https://files.pythonhosted.org/packages/01/3b/9a5fe6691f8f6d72899cdc2713ffbd845b8c6981eeeab66d98a71b721116/uuid_utils-0.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44020a4532229ccfbba353138539774686350dda71cf4368e257973dd8ba403", size = 528376, upload-time = "2026-06-18T13:35:51.825Z" }, + { url = "https://files.pythonhosted.org/packages/87/ad/47c93dcabd00f6749803a00be361c75d7079c78ad5e67077dee63d30b687/uuid_utils-0.16.2-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:280d4f1f22dd2e79c1cc31ffc7fc26dc3534ffc114dedcdd29cc8489c5ce9c98", size = 98033, upload-time = "2026-06-18T13:35:53.385Z" }, + { url = "https://files.pythonhosted.org/packages/c0/fd/8de85eeb8dd59354ad46e897ab0d0f0fe6bc48702239a6c9f2613f961c8e/uuid_utils-0.16.2-cp313-cp313-win32.whl", hash = "sha256:4942b26ad12c5187bac52b7fb4685040139ff0df9a19cde33e5025326f6180fc", size = 167054, upload-time = "2026-06-18T13:35:54.495Z" }, + { url = "https://files.pythonhosted.org/packages/86/b3/b5ba393fbe5142eb9d5db23d4b9b16dde2a4e1aee6f2fcb7fadef97e419a/uuid_utils-0.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:01f81c71cf2185de0707e9d2f248e17025ba50af0acd3cbf51cd8aea96c2e0be", size = 173481, upload-time = "2026-06-18T13:35:55.684Z" }, + { url = "https://files.pythonhosted.org/packages/b2/79/4e5d63d605b13201ae9af6fcc36ec77949cccc99486c430c016d8f8ed274/uuid_utils-0.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:c1dbe65ce6d46c5f645356d64bfb2de7564e2426ca8c9b1a0a401d6f7ae5cc22", size = 172197, upload-time = "2026-06-18T13:35:56.817Z" }, + { url = "https://files.pythonhosted.org/packages/89/3a/0e5a0c1e1e3243cf5f12efd2b88a33e63c38b6a79483d3c84b2f5e7265cf/uuid_utils-0.16.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:617955f4b3f649617c0388127d8a257202189d5cc3c720313f8b207df1cdb2a4", size = 566227, upload-time = "2026-06-18T13:35:57.925Z" }, + { url = "https://files.pythonhosted.org/packages/28/b3/2b6f9d6832e939aaf2b2ba89ff70b3994cfa3ae9b14daac3329eb9202ef8/uuid_utils-0.16.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:0aa2569908bdb21ccb216cd6bd06cb934351ee65ea7cd5e351e19f633a99b577", size = 290301, upload-time = "2026-06-18T13:35:59.467Z" }, + { url = "https://files.pythonhosted.org/packages/f5/27/8bb31429884b9f340f964ed70b68bfd81cec61f6e6877633f6a014358e78/uuid_utils-0.16.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4af7673e84e1ec6029f18d3a0408095c471c4e2691b6e46b4e1f0a2051734ba", size = 325409, upload-time = "2026-06-18T13:36:00.786Z" }, + { url = "https://files.pythonhosted.org/packages/1f/87/3b59aa97e788ca4fa46e2a3856ef567b51e03fd7fbf27d39ce36e46478b6/uuid_utils-0.16.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ecadf55ed6b8fb72e7966b52fd02919e7d7bb8e7bffeaf285803b82e774debfb", size = 332071, upload-time = "2026-06-18T13:36:02.043Z" }, + { url = "https://files.pythonhosted.org/packages/1c/21/8c21bf6cf3ce9447b73cee6a38ca63c9bb2f3145259422646bae8e8ddc21/uuid_utils-0.16.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:026b96b2f1e6b004579e030692d2f6568ccd0b29d40687213c31694abf570c78", size = 447075, upload-time = "2026-06-18T13:36:03.305Z" }, + { url = "https://files.pythonhosted.org/packages/95/43/77e83019effe1a5ab7169a2d4bf1bd654bebd850b81c8a937b96bd6b5c9c/uuid_utils-0.16.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:273679723e88544dd2de0564ab7f2fddfa2270faf05cabfdf63c275be67ec2a1", size = 325061, upload-time = "2026-06-18T13:36:04.972Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a6/7bf6e0165dc191c09bc4e8c011de5463d64c5a651ed38ad6698bfc552a52/uuid_utils-0.16.2-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec5b1a338b92d1eb121e9eaf06ae3db1b9a5cd794ce318a475f6dc6f9e89c3a8", size = 350302, upload-time = "2026-06-18T13:36:06.172Z" }, + { url = "https://files.pythonhosted.org/packages/45/66/260836aaef14b8254bc449b3163fedec06ef0a0bba0d6a999c918479b2f9/uuid_utils-0.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e75f9429d4533ce275c98bc68bf47fb237ae7b32c954266dabc5edab0c7d682e", size = 501834, upload-time = "2026-06-18T13:36:07.469Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0b/84c1542bf8c465b456f742318ad83eace63551e7f603b06c817b726670af/uuid_utils-0.16.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f3cca9ca5e2c2dfd7b885f0d34c10b993a070d3593f3cdfef785195da36fb0f", size = 607406, upload-time = "2026-06-18T13:36:08.913Z" }, + { url = "https://files.pythonhosted.org/packages/48/7f/1024c22657a0c0572c4fd5189fad3127cb46731fb26fad3be1e8a4a64972/uuid_utils-0.16.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1ef8c561fdf88fec205e3d54037824cfe2addce16b509a8d2ecb69daa904cbb7", size = 567623, upload-time = "2026-06-18T13:36:10.14Z" }, + { url = "https://files.pythonhosted.org/packages/15/0e/ad7424a6444e3e108a22781c2e164e82752da5db23ccc5cba8b4470c3164/uuid_utils-0.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3e3acb5e1451232381daea01645a98c69de4bb9ad88d77a1f7c1df4d83d54e62", size = 530659, upload-time = "2026-06-18T13:36:11.649Z" }, + { url = "https://files.pythonhosted.org/packages/69/60/cf1666d0dbd6fa869b6de3b85a17254ff0ab10ed286fd59366148bf08e89/uuid_utils-0.16.2-cp314-cp314-win32.whl", hash = "sha256:b5f8e7d0bb2c6e6180176237f92d2e949626e04fcf701c49d73f128e1f64e1d1", size = 169272, upload-time = "2026-06-18T13:36:12.846Z" }, + { url = "https://files.pythonhosted.org/packages/fe/5e/111908bdc7287b2589e9a9f10be8e0358844fb4a0554677cbbe0ade49766/uuid_utils-0.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:bf922bad7df257336b594d316a1657df569860bb5389602919001fa6fb17f06e", size = 175435, upload-time = "2026-06-18T13:36:14.114Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/b3bd7415622060dd17d587545e3c037f83dc0dffb8880ac798ca7936f630/uuid_utils-0.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:fad82e6482129c58ba9b00da6c247ab6e767645ab17981599229cce19d7b2ce9", size = 173553, upload-time = "2026-06-18T13:36:15.561Z" }, + { url = "https://files.pythonhosted.org/packages/92/43/401acf6fc0e0665dd11a095a28f6d22708c6f8f148c326cfc5b0b1ae9882/uuid_utils-0.16.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e0609e7e906c08386b7f33141254df05dcab24f1c4884150988dc7a287516aca", size = 567548, upload-time = "2026-06-18T13:36:16.848Z" }, + { url = "https://files.pythonhosted.org/packages/4e/2c/cc2bb8273d414d651acafccc3705a8843c130a541fcce65fbeaac22266ba/uuid_utils-0.16.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:9ad2adeb941292fe02e1e5c70b80a5746c45b1b77594506c2a1421455d8384f9", size = 291348, upload-time = "2026-06-18T13:36:18.145Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a8/fdadd7ada0de53dbc03f719da0948cc275abd24d8013a26e42e50d3665c1/uuid_utils-0.16.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d906c00f965d5c5f4812d0086dc49bf813285ea84c97e8816405200e146f805b", size = 325495, upload-time = "2026-06-18T13:36:19.417Z" }, + { url = "https://files.pythonhosted.org/packages/16/42/e397a1eda06b20dd3a206e3a55b346ff2caad23906586801a87359530864/uuid_utils-0.16.2-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a59205fc15463dd0f978f14df14307737e3d4e8ef4aefa29a9d0fa766d84d16b", size = 332301, upload-time = "2026-06-18T13:36:20.747Z" }, + { url = "https://files.pythonhosted.org/packages/46/be/12d3df7bd824e3ce71630c022184a5aecfea92b0a7fa70459542b237777a/uuid_utils-0.16.2-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac82500329ffaf2788dac36cf133e1e4e23b6d5e1118274ea6749c3b512f4f1", size = 446760, upload-time = "2026-06-18T13:36:22.198Z" }, + { url = "https://files.pythonhosted.org/packages/f7/10/0c5d1dd6874fa35e2cb66a8499ce303eb8678bef226951182603bd30017d/uuid_utils-0.16.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d8257329f26905f009aed694bd3b17f334f43748b03134dc7bc99d6c5b4e371", size = 325781, upload-time = "2026-06-18T13:36:23.566Z" }, + { url = "https://files.pythonhosted.org/packages/04/e2/9ebb8414875e5c14737fa7145a023458c9b15754f1d129cefe7824197256/uuid_utils-0.16.2-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e04b5c10c6fcf9d9801084d1e86c9d7ada7eb48fe07ee4ae5e7fe5b1a852db8a", size = 351189, upload-time = "2026-06-18T13:36:25.09Z" }, + { url = "https://files.pythonhosted.org/packages/1b/5c/168d1f4d30b33c08365debfe4176c2f713a0940f1f11a64128a186d050c6/uuid_utils-0.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3d4805c4739dd06d539f8f4fa94f5aaf26eca4b3ece1ef134d4ff904c6b08dcf", size = 501866, upload-time = "2026-06-18T13:36:26.31Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8d/003865d5ed5bf82ece80bd61edb2692985f7548051749fd10f34edb16705/uuid_utils-0.16.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:76632d2e16e26de777851ec07961ceaea14e65167d0603a0b17fb169fa9ca37b", size = 607632, upload-time = "2026-06-18T13:36:27.704Z" }, + { url = "https://files.pythonhosted.org/packages/ea/52/6102f21f28323b27122a6aa3d4cea183b4fc401868c5c40767e1b9f53beb/uuid_utils-0.16.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6c02f85f49c9c2abbf247a8622458c30232332a28711755aa191da5f38015af6", size = 568216, upload-time = "2026-06-18T13:36:29.377Z" }, + { url = "https://files.pythonhosted.org/packages/68/50/644e4e55f47048d12bc20665fac85bc1fecbed9c892acfb91626abf8ad8d/uuid_utils-0.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f668035ea9faa763e8f1ea42040e8439db88cf2517056d47c348a62a257a1d02", size = 531370, upload-time = "2026-06-18T13:36:30.804Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5d/d98d99f601d70cc00287dce5aadef9c199912f0d64343962542f35e7db59/uuid_utils-0.16.2-cp314-cp314t-win32.whl", hash = "sha256:62b8841895eff1c0afbaf5f0050411667231160478c8ff9f411742abffd3b619", size = 169424, upload-time = "2026-06-18T13:36:32.246Z" }, + { url = "https://files.pythonhosted.org/packages/a6/af/c0d482bdd637a8a742d3274cec462b770919f032e179216f2fc2851afaf9/uuid_utils-0.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:e9064805881c30dd80a4189a0da7130e3d684de353ea36edd99c1b994bdf429e", size = 175544, upload-time = "2026-06-18T13:36:33.75Z" }, + { url = "https://files.pythonhosted.org/packages/86/fc/aff8b0456e8a63672fa89ea9c773f7547a31ff7b596a40f226bf148921a3/uuid_utils-0.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:3324bac95084e63e28553c92fac5a0394c636a76e03e50a7dab0c0bbddf87fa5", size = 173972, upload-time = "2026-06-18T13:36:35.076Z" }, + { url = "https://files.pythonhosted.org/packages/0e/48/8c9fee7d75571f2f4b2386eac798fe5f826155d13797f7c86d45eb3fdc23/uuid_utils-0.16.2-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8b8e325e61f918caf74ca540e3384b81e6e22aea782e57f615d15fc9773b96c8", size = 571003, upload-time = "2026-06-18T13:36:36.42Z" }, + { url = "https://files.pythonhosted.org/packages/de/78/754eaaa49509be6fdb705de61d1e3889de32002132d5f00e8c1e5d212da3/uuid_utils-0.16.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9282677ebf2ea5b437c20d16e75bcd7629bdc205018f95557b33b76868d8bb5b", size = 290244, upload-time = "2026-06-18T13:36:38.066Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e2/bfcbcf7eb9dfb17701104c569ed771eb359737bc70b7309e439610d089ef/uuid_utils-0.16.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e9ca7f5e215373cc9c147172170a0b1a4ab0dee9cc62fe446d9b075f31e3241", size = 328551, upload-time = "2026-06-18T13:36:39.605Z" }, + { url = "https://files.pythonhosted.org/packages/72/bf/bbdbc39d1421953edcee0bad13a1893521a636eccf381580f53e530a4feb/uuid_utils-0.16.2-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43cc72a92694d08ade8faadacf928857d9cceb84b449473246ae4e4f263d7d22", size = 335468, upload-time = "2026-06-18T13:36:41Z" }, + { url = "https://files.pythonhosted.org/packages/04/2a/e8d4e6f1f2d2e567cf6e3202d125afe7da52ad7680bba048b106c09f01b9/uuid_utils-0.16.2-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511b5fde12d29c37a9badd399af62105bb2f4696aa10eb18be74e7b9ca84413a", size = 450984, upload-time = "2026-06-18T13:36:42.635Z" }, + { url = "https://files.pythonhosted.org/packages/6a/da/ddb1dcf0fe9bfcb0dfcddec8ae52c8f95e7088e44719f58477f5fb2c5586/uuid_utils-0.16.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:585d3adf73afa60348bf2bd529491c640a692350e76d8ff3974455e273aadfe7", size = 327940, upload-time = "2026-06-18T13:36:44.138Z" }, + { url = "https://files.pythonhosted.org/packages/37/fb/39305fbfffee1fdaccdb88fc0499ac9dcb7289a77ebc31938dcdd933cf95/uuid_utils-0.16.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ae5fa2007fd26d26f7b09e76259d5ca99bec191616207ca929f8dca12da08129", size = 355368, upload-time = "2026-06-18T13:36:45.682Z" }, + { url = "https://files.pythonhosted.org/packages/b4/70/b708edc3b776d7624b4354f43d443f14d951d3ac4d7d8867d94f2e59c3ae/uuid_utils-0.16.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9b4520521aa46a2582fe1829c535fe60b78999b89257db998df3816eb895bdf3", size = 178221, upload-time = "2026-06-18T13:36:47.291Z" }, +] + +[[package]] +name = "uv" +version = "0.11.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/8e/2777bf40df3c634f2a522baaa2995d2bd5901461fc5d8730c04c39fa6c75/uv-0.11.25.tar.gz", hash = "sha256:458e731778e7b5cc870710397859c23e766703e7bc0695f23b3eb15080745ba6", size = 4329198, upload-time = "2026-06-27T00:49:00.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/a7/dc836daca0db76178cfe4e40ee95fa2c5f96a3d7b3cda0bdde5291aef8b8/uv-0.11.25-py3-none-linux_armv6l.whl", hash = "sha256:d6f965a79fc7539a12139ce981caa0cbf7d9d3bd4ea3daadaf174ab4d7fb6e42", size = 25153689, upload-time = "2026-06-27T00:48:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/d60eabb616db232ed0e682c55d858d0980175a92336429c4e0ee82a160e6/uv-0.11.25-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:610650cbaa0a9b18015da39d2c28d736d287a5a124e49296d8fdef5e4022e980", size = 24149272, upload-time = "2026-06-27T00:48:13.917Z" }, + { url = "https://files.pythonhosted.org/packages/ab/fd/9f72373e340c6abcd3eb9257d69b442434b65e90e1f7f53f333abdac11b2/uv-0.11.25-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f7a78fc8d0c5e764e9fa39c99066db47a0bc465b023feed90812e3c0a6b5eb0d", size = 22885453, upload-time = "2026-06-27T00:48:16.641Z" }, + { url = "https://files.pythonhosted.org/packages/ee/c1/7cbb3f6b4a021ce7c1c453d1412d3b4b68d7d7585d15d91a57e9ab0a317e/uv-0.11.25-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:e3480640983e0b8e509eeb67882837e620bdd820f8776948a5f13ebbb4481d04", size = 24935172, upload-time = "2026-06-27T00:48:19.495Z" }, + { url = "https://files.pythonhosted.org/packages/df/d4/862a534ab4aae28ad2c9145ea5af3021eefe225e4e1c983278bb34b53b9e/uv-0.11.25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:b180b12237b4e04692491fc6796584a9a8bdf4c7332bd2a769caf096b97885d0", size = 24665125, upload-time = "2026-06-27T00:48:22.408Z" }, + { url = "https://files.pythonhosted.org/packages/91/2a/70ef2f2a212d89d23f90e1c8017c080fae93bec21cd9f14795498a6e6f89/uv-0.11.25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:69d14ffd0a4b050f8a70f64aacb09b8dfdfb1cb30a6351fb17b48f273f95c58c", size = 24653836, upload-time = "2026-06-27T00:48:24.858Z" }, + { url = "https://files.pythonhosted.org/packages/44/2b/a7beea057778f092daa5aeda7aeb3f8bfa78f4a0622245adb449fdbb3d20/uv-0.11.25-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61ef11d9967a38109e6e8e3d20d1f743fa08033c32bce274d6ccd9a9abb5d305", size = 26069304, upload-time = "2026-06-27T00:48:28.05Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e8/bb8ab595a27035b565cedd5d601ae2b85cf6aba6f68cd6a97eae88dd7250/uv-0.11.25-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3febca65ec5bc336ddaf7e4f724704f2c894c16839723df14865ee00b4acf38d", size = 26992784, upload-time = "2026-06-27T00:48:30.584Z" }, + { url = "https://files.pythonhosted.org/packages/17/90/bbfa0653e992877e5ea78ab3186fb6a5a122ebef5913b6809ef6d06fcf9b/uv-0.11.25-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:850ba0018ff170c3a9baaf9b5fe8b23393b6b77ee4ea6b2e2315fdb8d7c388f7", size = 26151002, upload-time = "2026-06-27T00:48:33.739Z" }, + { url = "https://files.pythonhosted.org/packages/91/01/f5a01fd777ce501cc59eb71775f3bbacac258a65a64939f07804c2279c98/uv-0.11.25-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2bc05e17ae3e1f232abf93e7dcfb3b68702dfcde34a00c29cbce7e07d1ecbfb", size = 26334122, upload-time = "2026-06-27T00:48:36.247Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c4/f8b3b6b1bb48a452d98c02909e229f7ae317300618d6dc334c883742339e/uv-0.11.25-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:57fbd47e924242fd347d0c209d95711d8ea61db8d8780962d0f30ccde2c854a3", size = 25055505, upload-time = "2026-06-27T00:48:39.418Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7d/aece221faba22804d8b4f913903358dc2d45193babf2a44a49f79eadd25b/uv-0.11.25-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:f42de9e7d63a28a4fe76a522077813656de38b5acda20b4db63857d260c1ff13", size = 25660097, upload-time = "2026-06-27T00:48:42.398Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7d/b835ed56eab281054efbfbcbbe1249621908c6e4c721a878904cbed2f418/uv-0.11.25-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2c1cfe97dce56c997dfa3214bdb8955b7b34cceea7505520185e22ad99c0eb6b", size = 25759374, upload-time = "2026-06-27T00:48:45.069Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fd/28c990fd18330aa01383bae738be113f4ac5d9d414f92790947326c6781c/uv-0.11.25-py3-none-musllinux_1_1_i686.whl", hash = "sha256:fbff70ae9fa4da9fb6823ae4fdaf77a65c9520e13b6d1d0241ba56e4b121b7aa", size = 25329343, upload-time = "2026-06-27T00:48:47.718Z" }, + { url = "https://files.pythonhosted.org/packages/70/48/16ff9dbe5f308cb547e227971cbcaf3c905797da12a4116b620e2acc09cb/uv-0.11.25-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:41b37e724f41eb4c3794bbdd82ddeebb4b5850d4ada8cccb2906ef9e5aa0f83b", size = 26477125, upload-time = "2026-06-27T00:48:50.251Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8b/4146a55d3ee6b2b67c3dece3ef6be220c443abafc2dc56baa2f74d6b36a8/uv-0.11.25-py3-none-win32.whl", hash = "sha256:86d4759fec9b46f61944d6e9ef1f5eaa2c5fbe2db5ddb59492d9174b08fcf39c", size = 23896598, upload-time = "2026-06-27T00:48:53.01Z" }, + { url = "https://files.pythonhosted.org/packages/64/66/784f9fb457b640dbb96cdd175f4902235b42ffe740dbaee9ea5fc649d8b2/uv-0.11.25-py3-none-win_amd64.whl", hash = "sha256:560b0fbaa6356af533923a349658c21d4f410d16e835787d8a05da451d4ee859", size = 26862205, upload-time = "2026-06-27T00:48:55.667Z" }, + { url = "https://files.pythonhosted.org/packages/85/cb/d7bb121261db0b829f38b1e86b80d8103afd050b8a5c5c9fe4a988d2f1fc/uv-0.11.25-py3-none-win_arm64.whl", hash = "sha256:79f166cd1b84f855e9d2768221d59b403869648289fd884d58ad4299edfb4d9e", size = 25153588, upload-time = "2026-06-27T00:48:58.495Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.49.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, + { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, + { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/3d/8024c801df84d1587740d0359e7fdd80afeae3d159011f3d5376dd82f18e/watchfiles-1.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201", size = 400242, upload-time = "2026-05-18T04:31:19.014Z" }, + { url = "https://files.pythonhosted.org/packages/87/5b/f4dfd45323e949984a3a7f9dc31d1cbb049921e7d98253488dda72ccdaa9/watchfiles-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5", size = 394562, upload-time = "2026-05-18T04:30:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/19483ef075d601c409bce8bcbb5c0f81a10876fff870400568f08ce484a1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a", size = 456611, upload-time = "2026-05-18T04:30:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6a/cc81fbe7ee42f2f22e661a6e12def7807e01b14b2f39e0ff83fd373fd307/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1", size = 461379, upload-time = "2026-05-18T04:31:29.292Z" }, + { url = "https://files.pythonhosted.org/packages/b1/57/7e669002082c0a0f4fb5113bb70125f7110124b846b0a11bc5ae8e90eac1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717", size = 493556, upload-time = "2026-05-18T04:30:05.44Z" }, + { url = "https://files.pythonhosted.org/packages/45/7d/f60a2b19807b21fe8281f3a8da4f59eef0d5f96825ac4680ba2d4f2ebf91/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b", size = 575255, upload-time = "2026-05-18T04:30:40.568Z" }, + { url = "https://files.pythonhosted.org/packages/bd/49/77f5b5e6efbcd57482f74948ebb1b97e5c0046d6b61475042d830c84b3ff/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5", size = 467052, upload-time = "2026-05-18T04:31:17.942Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5a/73e2959af1b97fd5d556f9a8bdba017be23ceeef731869d5eaa0a753d5a3/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e", size = 456858, upload-time = "2026-05-18T04:30:30.182Z" }, + { url = "https://files.pythonhosted.org/packages/50/57/1bc8c27fad7e6c19bddee15d276dbb6ab72480ec01c127afff1673aee417/watchfiles-1.2.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165", size = 467579, upload-time = "2026-05-18T04:32:15.897Z" }, + { url = "https://files.pythonhosted.org/packages/09/6c/3c2e44edba3553c5e3c3b8c8a2a6dee6b9e12ae2cf4bd2378bebf9dc3038/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6", size = 633253, upload-time = "2026-05-18T04:31:37.123Z" }, + { url = "https://files.pythonhosted.org/packages/30/c2/d8c84a882ab39bbefcc4915ab3e91830b7a7e990c5570b0b69075aba3faf/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5", size = 660713, upload-time = "2026-05-18T04:31:24.62Z" }, + { url = "https://files.pythonhosted.org/packages/a9/07/f97736a5fc605364fe67b25e9fa4a6965dfd4840d50c406ada507e9d735f/watchfiles-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8", size = 277222, upload-time = "2026-05-18T04:31:21.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/99/2b04981977fc2608afd60360d928c6aecf6b950292ca221d98f4005f6694/watchfiles-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22", size = 290274, upload-time = "2026-05-18T04:31:45.966Z" }, + { url = "https://files.pythonhosted.org/packages/3c/74/f7f58a7075ee9cf612b0cfcddb78b8cd8234f0742d6f0075cf0da2dde1c6/watchfiles-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7", size = 283460, upload-time = "2026-05-18T04:31:39.126Z" }, + { url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" }, + { url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" }, + { url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" }, + { url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" }, + { url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" }, + { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" }, + { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" }, + { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" }, + { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" }, + { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" }, + { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" }, + { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" }, + { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" }, + { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" }, + { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, + { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, + { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, + { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, + { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, + { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, + { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/23/f4/7513ef1e85fc4c6331b59479d6d72661fc391fbe543678052ac72c8b6c19/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2", size = 403050, upload-time = "2026-05-18T04:30:36.753Z" }, + { url = "https://files.pythonhosted.org/packages/27/0b/a54103cfd732bb703c7a749222011a0483ef3705948dae3b203158601119/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db", size = 396629, upload-time = "2026-05-18T04:32:03.268Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2c/73f31a3b893886206c3f54d73e8ad8dee58cdb2f69ad2622e0a8a9e07f4e/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7", size = 457318, upload-time = "2026-05-18T04:31:01.932Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f9/45d021e4a5cc7b9dd567f7cbb06d3b75f751a690063fb6cc7ec60f4e46b7/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0", size = 457771, upload-time = "2026-05-18T04:30:56.331Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "wrapt" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/a4/282c8e64300a59fc834518a54bf0afabb4ff9218b5fa76958b450459a844/wrapt-2.2.2.tar.gz", hash = "sha256:0788e321027c999bf221b667bd4a54aaefd1a36283749a860ac3eb77daed0302", size = 129068, upload-time = "2026-06-20T23:49:44.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/15/0c2d55168707465abfc41f33c0b23d792a5fa9b65c26983606940900a120/wrapt-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f1a2ff355ece6a111ca7a20dc86df6659c9205d3fcee674ca34f2a2854fd4e73", size = 80782, upload-time = "2026-06-20T23:47:44.367Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b5/5c0b093eb48f8a062ef6267d3cb36e9bb1b88440181f6545a383c60efdf8/wrapt-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55b9a899e6fff5444f229d30aa6e9ac92d2216d9d60f33c771b5d76a760d5f8e", size = 81678, upload-time = "2026-06-20T23:47:45.857Z" }, + { url = "https://files.pythonhosted.org/packages/34/f3/de70937472dd3e8a4e6811192f9c6075efdffd4a2cd9b4596bf160f89668/wrapt-2.2.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a2d78c363f97d8bd718ee40432c66395685e9e98528ccaa423c3355d1715a26d", size = 159671, upload-time = "2026-06-20T23:47:47.345Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/40aed2330e7f02ecf74386ffcfef9ccb7108c6a430f15b6a252b663b1bed/wrapt-2.2.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d619e1eed9bd4f6ed9f24cd61971aa086fa86505289628d464bcf8a2c2e3f328", size = 160785, upload-time = "2026-06-20T23:47:48.759Z" }, + { url = "https://files.pythonhosted.org/packages/45/04/aa5309beed5344b00220ae6b3b24055852192656194c27947bee1736306a/wrapt-2.2.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:518b0c5e323511ec56a38894802ddd5e1222626484e68efe63f201854ad788e5", size = 153699, upload-time = "2026-06-20T23:47:50.177Z" }, + { url = "https://files.pythonhosted.org/packages/01/df/2def7e99d1fe87eea413f95f671924cdddcb08823b1ffd212748dfa6d062/wrapt-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4bccea5cdecffa9dd70e343741f0e41e0a16619313d04b72f78bb525162ebcd0", size = 159695, upload-time = "2026-06-20T23:47:51.602Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f6/a906d01a2ce12157bad2404957b3e2140da354b8a70b2fa48bbf282871c0/wrapt-2.2.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:209112cafd963710a05d199aae431d79a28bc76eb8e6d1bbbb8ad24340722cae", size = 152813, upload-time = "2026-06-20T23:47:53.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/49/bc0086292d239575b4c08f4cf8a4079fa58abbad58ec23abf84833a283ed/wrapt-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5a5290e4bf2f332fc29ce72ffb9a2fff678aaac047e2e9f5f7165cd7792e099", size = 158809, upload-time = "2026-06-20T23:47:54.391Z" }, + { url = "https://files.pythonhosted.org/packages/55/83/8fbd034de1f3e907edaa18786d5dd8f6932874edee0826c7cecb5cab03a1/wrapt-2.2.2-cp311-cp311-win32.whl", hash = "sha256:5499236ad1dc116012e2a5dd943f3f31af12fce452128e2bbcbd55a7d3d4d14c", size = 77414, upload-time = "2026-06-20T23:47:55.882Z" }, + { url = "https://files.pythonhosted.org/packages/7e/9c/23695baa331c6de4e874c3d78b8e0bed92e1d2a274e665b29858f6841672/wrapt-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:8636809939152be6ae20a6cef0fed9fe60f411b47847d0426a826884b469e971", size = 80368, upload-time = "2026-06-20T23:47:57.237Z" }, + { url = "https://files.pythonhosted.org/packages/08/49/40cefc342bf89b234a4490d741290fce781774b831aefb39c25471da96c9/wrapt-2.2.2-cp311-cp311-win_arm64.whl", hash = "sha256:5d0a142f7af07caeb5e5da87493162a7b8efa19ba919e550a746f7446e13fb30", size = 79489, upload-time = "2026-06-20T23:47:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/2a/85/180b40628b23772692a0c76e8030114e1c0ae068470ed531919f0a5f2a4a/wrapt-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8417fd3c674d3c8023d080292d29301531a12daf8bd938dd419710dd2f464f2b", size = 81484, upload-time = "2026-06-20T23:47:59.924Z" }, + { url = "https://files.pythonhosted.org/packages/94/f2/21c90f2a16689702e2aaff45795b11018dff2c9b1242bac10d225483f676/wrapt-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e7070c7472582e31af3dfc2622b2381a0df7435110a9388ed8db5ffbce67efb", size = 82151, upload-time = "2026-06-20T23:48:01.303Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b3/7e6e9fcf4fe7e1b69a49fe6cc5a44e8224bab6283c5233c97e132f14908e/wrapt-2.2.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2e096c9d39a59b35b63c9aacfbbbec2088ff51ff1fc31051acc60a07f42f273a", size = 169828, upload-time = "2026-06-20T23:48:02.719Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/894f132d857ed5a9904d937baf368badcbe5ea9e436e2f1930fe21c9f1f0/wrapt-2.2.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d1a6050405bf334be33bf66296f113563622972a34900ae6fa60fd283a1a900", size = 171544, upload-time = "2026-06-20T23:48:04.266Z" }, + { url = "https://files.pythonhosted.org/packages/29/de/3c833e03725b477e9ea34028224dd21a48781830101e4e036f77e8b6b102/wrapt-2.2.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10adb01371408c6de504a6658b9886480f1a4919a83752748a387a504a21df79", size = 160663, upload-time = "2026-06-20T23:48:05.708Z" }, + { url = "https://files.pythonhosted.org/packages/33/be/27edce350b24e3054d9d047f65f16d4c4d4c1f3f31c4278a1f8a95c723c8/wrapt-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3442eee2a5798f9b451f1b2cd7518ce8b7e28a2a364696c414460a0e295c012a", size = 169387, upload-time = "2026-06-20T23:48:07.243Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c4/9fd9679af8bf38e146652c7f47b6b352c3e5795b4ad1c0b7f94e15ac2aa7/wrapt-2.2.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6c99012a22f735a85eed7c4b86a3e99c30fdd57d9e115b2b45f796264b58d0bf", size = 158849, upload-time = "2026-06-20T23:48:08.91Z" }, + { url = "https://files.pythonhosted.org/packages/bc/c2/aa6c0c2206803068c6859dabe01f8c84c43744da93d4c67b8946d21655ee/wrapt-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b686cfc008776a3952d6213cb296ed7f45d782a8453936406faa89eac0835ab", size = 168147, upload-time = "2026-06-20T23:48:10.374Z" }, + { url = "https://files.pythonhosted.org/packages/42/63/3eb25da41049d20ae18fcab2dd8b056e02387c4bfa626cbdfb7c3b872e4f/wrapt-2.2.2-cp312-cp312-win32.whl", hash = "sha256:ef2cce266b5b0b07e19fa82e59673b81142b7a3607c8ed1254113d048ed668da", size = 77734, upload-time = "2026-06-20T23:48:11.769Z" }, + { url = "https://files.pythonhosted.org/packages/da/09/0390e008a305360948fa9ce69507d041ac12cb2ee5d28e34467e2ee79391/wrapt-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:abf8c20a2d72ee69e16328b3c91342c446e723bfe48bfcc4dded3b9722ac027f", size = 80585, upload-time = "2026-06-20T23:48:13.117Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b3/84c445c66969f2d3457276b183a48c91097d59bbef9af6c075366b0f8c36/wrapt-2.2.2-cp312-cp312-win_arm64.whl", hash = "sha256:c6c64c5d02578bc4c4bca4f0aef1504de933c1d5b4ac2710b9131111459506c8", size = 79553, upload-time = "2026-06-20T23:48:14.5Z" }, + { url = "https://files.pythonhosted.org/packages/43/fc/f32f4b22c6511173c11d9e541ab4e7d8467a0f1b3455acaf784115d31ff8/wrapt-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9e8b648270c613720a202d9a45ebabc33261b22c3a839b115ac5bce8c0bb0d69", size = 81296, upload-time = "2026-06-20T23:48:15.881Z" }, + { url = "https://files.pythonhosted.org/packages/72/06/4d117d5d77a9344776c0248b24dae3d3dd2f58e5f765fa08cf887072e719/wrapt-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6fb7e94e8fe3e4c3067bb1653a91cce7c5e83acc119fdd41501b1bf74654617", size = 81841, upload-time = "2026-06-20T23:48:17.262Z" }, + { url = "https://files.pythonhosted.org/packages/15/ff/63ad96f98eb58a742b1a20d80f21da88924405910149950b912368150468/wrapt-2.2.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb18fc51e813df0d9c98049e3bf2298a5495a648602040e21fa3c7329371159e", size = 167882, upload-time = "2026-06-20T23:48:18.764Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/8bb62d8933df7acf3247194e6e9fc68edf9d2fa203252c89c94b319dd472/wrapt-2.2.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b00b00f806eb3ef2abe9049ed45994a81ee9284884d96e6b8314927c6cea3d", size = 167411, upload-time = "2026-06-20T23:48:20.315Z" }, + { url = "https://files.pythonhosted.org/packages/17/09/8789dcb09ee1de715727db7521aabbb68ffa68dfade3a49468440cfced49/wrapt-2.2.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:62415fd095bc590b842b6d092f2b5d9ccbaeb7e0b28535c03dcea2718b48636b", size = 158607, upload-time = "2026-06-20T23:48:21.728Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/66e02562d53ee67d841f175e38e3c993c2d78a3e104c576cad61c028b43c/wrapt-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a41e758d80dc0ab8c210f641ac892009d356cf1f955d97db544c8dd317b4d14c", size = 166367, upload-time = "2026-06-20T23:48:23.177Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a3/832ac4e41222fb263b3042d42c2f08d305db7d0f0c9b1d3a271a9eede8f6/wrapt-2.2.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b84cd4058001c9727b0e9980b7a9e66325b5ca748b1b578e822cade1bc6b304f", size = 157176, upload-time = "2026-06-20T23:48:24.711Z" }, + { url = "https://files.pythonhosted.org/packages/b7/01/1bd5e4d2df9c0178989ac8da9186543465388588ee2ef153e2591accebef/wrapt-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26fc73a1b15e0946d2942b9a4426d162b51676338327dc067ccd8d2d76385f94", size = 167025, upload-time = "2026-06-20T23:48:26.118Z" }, + { url = "https://files.pythonhosted.org/packages/1c/69/583ed25291ab53e1ec117135fb1c33425e2f46d2bc8f29c17f7a94cf4274/wrapt-2.2.2-cp313-cp313-win32.whl", hash = "sha256:3c4095803491f6ef72128914c28ec05bbad9758433bb35f6715a3e9c8e46fb2d", size = 77605, upload-time = "2026-06-20T23:48:27.643Z" }, + { url = "https://files.pythonhosted.org/packages/29/68/e69fc6d06e1523c68e0d00f95c9aed1158ce9908ee41603f7f2eae3d5db6/wrapt-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:2cb07f414fab25dbe6b5c7398e1491423a5c81a6209533639969a6c928d474a4", size = 80508, upload-time = "2026-06-20T23:48:29.013Z" }, + { url = "https://files.pythonhosted.org/packages/55/21/fe7a393d9e5dc0923bed8f5d857e9dcff210f1fa0888c02cc8f3ffaa55aa/wrapt-2.2.2-cp313-cp313-win_arm64.whl", hash = "sha256:1fc7691f070220215cccb2a20836b9adbaecb8ff22ad47abe63de5f110994fac", size = 79565, upload-time = "2026-06-20T23:48:30.429Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e5/c120d13bf5091164f68c3c1657e84f16f57e71d978421b626393ac5bd7eb/wrapt-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ec8f83949028366531383603139403cac7a826e4011955813cdd640017845ce5", size = 83264, upload-time = "2026-06-20T23:48:31.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b0/d4a1eb97e0e286625bdf21bc7f702637f9607787ffbbdb5ec14d50c79dbf/wrapt-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b481fb0c40d9fd90a5809911208da700987d373a20a4709dc9e3944af7a6bec", size = 83791, upload-time = "2026-06-20T23:48:33.482Z" }, + { url = "https://files.pythonhosted.org/packages/18/1e/f060df47755e87b57684cee7bfc1362b204df55fac96ffebc0631b697b79/wrapt-2.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0065a3b657cec06813b4241d2462ccec287f6863103d7445b725fb3a889736f9", size = 203399, upload-time = "2026-06-20T23:48:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/c4/de/2316a757a1abb6453700b79d83e532146dcef2611348282d4d8889792161/wrapt-2.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30f7424af5c5c345b7f26490e097f74a2ef45b3d08b664dc33571aee3bd3b56c", size = 210461, upload-time = "2026-06-20T23:48:36.569Z" }, + { url = "https://files.pythonhosted.org/packages/ed/29/d1160785ae18ca2495a6d82a21154103d74f656c9fd457fb35f6b11b965a/wrapt-2.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07fdcb012821859168641acf68afad61ef9783cf37100af85f152550e9677194", size = 195313, upload-time = "2026-06-20T23:48:38.175Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2d/7caa9598ae61a9cf0989cc501739cbeeb7d650ab3193cca1407b9af0c6ab/wrapt-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f90038ab58fafb584801ca62d72384d7d5225d93c76f7b773c22fae545bd8066", size = 206116, upload-time = "2026-06-20T23:48:39.804Z" }, + { url = "https://files.pythonhosted.org/packages/ac/02/281ea1088b8650d865f311b35cf86fd21df89128e2909714f1161e01c9d0/wrapt-2.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c5d7825491bfa2d08b97e9557768987952c7b9ae687d06c3320b40a37ccb7f20", size = 192668, upload-time = "2026-06-20T23:48:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/be/7d/976e2d5b4b5c5babda40974edd54d0a5585cb60132ed86b46f4b80239b16/wrapt-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ad520e6daa9bbf136f14de735474dbec7dcc0891f718e1d274ce8dc92e645af", size = 198891, upload-time = "2026-06-20T23:48:43.056Z" }, + { url = "https://files.pythonhosted.org/packages/59/b7/e47651797c097f75a37e2ce86dcf04048ff576f3a674f7c558df7b5e9622/wrapt-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:25904acb9475f46c24fe0423dbc8fda8cc5fbc282ab3dc6e72e919748c53f4e9", size = 78537, upload-time = "2026-06-20T23:48:44.509Z" }, + { url = "https://files.pythonhosted.org/packages/d1/6f/9fa5d59fb06d890defb5a8f727ce6a14d2932c8760153f96956628559fee/wrapt-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:305d4c247d61c4115794a169141823c62f719525ddb90b23aa332741c77d2c28", size = 82005, upload-time = "2026-06-20T23:48:46.391Z" }, + { url = "https://files.pythonhosted.org/packages/15/80/4c7bd9873d1f9f7d138d93556b500469dbe24f42710b877519c2b9eb380d/wrapt-2.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c20279cd1a29800815d7b2d6338b60a6c6e78263f9d6e62e0eda251ba9cae2d0", size = 80762, upload-time = "2026-06-20T23:48:47.964Z" }, + { url = "https://files.pythonhosted.org/packages/24/05/7fd9c3f83b2c74cbfc572a0b88aa37431e04bd8aed70d2c0efd3464206de/wrapt-2.2.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0e64826f920c42d9d9f87e8cc09ffae66c51ede12d59061a5a426deb9aa71745", size = 81341, upload-time = "2026-06-20T23:48:49.39Z" }, + { url = "https://files.pythonhosted.org/packages/4b/68/1bfa43100dd90d4ef74a05897b86275cf57e1313ca14aae2545bc9f872c9/wrapt-2.2.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dcaa5e1451bd8751d7bd1568dfa3321c78092a52a7ecb5d1a0f18a5791e1fd00", size = 81921, upload-time = "2026-06-20T23:48:50.986Z" }, + { url = "https://files.pythonhosted.org/packages/74/eb/df7b7f0b631dbbc750f39be27d8b55f65777d8ac86da80e12be41a644c4b/wrapt-2.2.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0abfd648dac9ac9c5b3aa9b523d27f1789046640b58dcd5652a720ddb325e1fc", size = 167713, upload-time = "2026-06-20T23:48:52.598Z" }, + { url = "https://files.pythonhosted.org/packages/4d/9a/d1bd36f6d088c8e652a9383cabbd49af30b8c576302a7eccddbab6963e3f/wrapt-2.2.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4bfd8d1eb438153eff8b8cfe87f032ba65731e1ce06138b5090f745a33f6f95", size = 166779, upload-time = "2026-06-20T23:48:54.33Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ae/24ffacd4187fac2740a1972093929e836dea092d42c87d728cd98fee11a6/wrapt-2.2.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c427c9d06d859848a69f0d928fe28b5c33a941b2265d10a0e1f15cd244f1ee33", size = 158407, upload-time = "2026-06-20T23:48:55.944Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ed/974427668249a356051e8d67d47fa54ef6c777f0fcf3bae9d292c047d4b6/wrapt-2.2.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4250b43d1a129d947e083c4dc6baf333c9bb34edd26f912d5b0457841fc858ab", size = 166594, upload-time = "2026-06-20T23:48:57.617Z" }, + { url = "https://files.pythonhosted.org/packages/fb/5f/e1d7c6e4523f78db2fbd7826babd0348da1d5e0834c4f918b9ab5757dfae/wrapt-2.2.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:173e5bb5ca350a6e0abab60b7ec7cdd7992a814cb14b4de670a28f067f105663", size = 157068, upload-time = "2026-06-20T23:48:59.171Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c1/7ebd1027f00700c0b0233b20aceef2b4784294ed64971424c4a78e069e34/wrapt-2.2.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aa14b01804bce36c6d63d7b6a4f55df390f29f8648cc13a1f40b166f4d54680d", size = 166470, upload-time = "2026-06-20T23:49:00.737Z" }, + { url = "https://files.pythonhosted.org/packages/99/eb/974e471a6a978b8180186b8a9dc5ae3361ce269a967190b709b8ce17abfb/wrapt-2.2.2-cp314-cp314-win32.whl", hash = "sha256:58f9f8d637c9a6e245c6ef5b109b67ec187d2faed23d1405656b51d96e0a5b56", size = 78062, upload-time = "2026-06-20T23:49:02.327Z" }, + { url = "https://files.pythonhosted.org/packages/49/ec/e1281156cdc7a66693838ad7a0865ad641c74abd337a957d668b575aaffb/wrapt-2.2.2-cp314-cp314-win_amd64.whl", hash = "sha256:385cb1866f20479e83299af585375bfa0a4b0c6c9907a981483ea782ea8ae406", size = 80832, upload-time = "2026-06-20T23:49:03.837Z" }, + { url = "https://files.pythonhosted.org/packages/45/7d/1b6b5ddd94005a2dac97a4490c9838f3154977850d633abcb65b30089437/wrapt-2.2.2-cp314-cp314-win_arm64.whl", hash = "sha256:8ffbeaea6771a6eba6e6eeb09767864995726bc8240bb54baf88a9bb1db34d5c", size = 80029, upload-time = "2026-06-20T23:49:05.237Z" }, + { url = "https://files.pythonhosted.org/packages/b0/33/9ebcf8aafe91c601127cbd93708c16aa8f688f34a10bf004046803ecdc4f/wrapt-2.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:09f811d43f6f33ec7515f0be76b159569f4057ab54d3e079c3204dddb90afa2a", size = 83357, upload-time = "2026-06-20T23:49:06.632Z" }, + { url = "https://files.pythonhosted.org/packages/39/38/ec45b635153327b52e52732a0ea980e5f00b7efba65f9e018828f1e69daa/wrapt-2.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a795d3c06e5fbf9ea2f13196180b77aeab1b4685917256ee0d014cc163d90063", size = 83794, upload-time = "2026-06-20T23:49:08.098Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ea/1a89e6d3b7a83c3affe5c09cde77792c947e63e4bc85ad84cd5bb9abb0d8/wrapt-2.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:45c2f2768e790c9f8db90f239ef23a2af8e7570f25a35619ef902df4a738447f", size = 203362, upload-time = "2026-06-20T23:49:09.811Z" }, + { url = "https://files.pythonhosted.org/packages/19/d8/3b58763d9863b5a73771c0d97110f9595d248db454009e07e1535ee905a4/wrapt-2.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbf00ee0cb55ec24e2b0995a71942b85b21a066db8f3f46e1dbfdb9433ffba81", size = 210449, upload-time = "2026-06-20T23:49:11.521Z" }, + { url = "https://files.pythonhosted.org/packages/2d/6f/17fd9e053103d8be148d20d5d7505facc72d5fe1f9127973904ceaed79cf/wrapt-2.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2252f77663651b89255895f58cc6ac08fcb206d4371813e5af61bb62d4f7689c", size = 195349, upload-time = "2026-06-20T23:49:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/d0d1ccaaa12cb7dccf28a23f0279a608ba498f71e81d949d5ed54bcfd5c1/wrapt-2.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2cd7181ab1c31192ff5219269830744b5a62020b3a6d433588c4f1c95b8f8bff", size = 206099, upload-time = "2026-06-20T23:49:15.051Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/e8aa07b619890a2aa6cde1931b1887abb08820721b564a5f80b7ca3f3aa0/wrapt-2.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6fe35fd51b74867d8b80174c277bd6bbf6a73e443f908129dc531c4b688a20d5", size = 192728, upload-time = "2026-06-20T23:49:16.854Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f0/1819fb50f0d3c9bd758d8a83b56f1b470dee8b5b8eac8702b7c137cea9d4/wrapt-2.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:11d95fc2fbad3163596c39d440e6f21ca9fccece74b56e30a37ac2fca786a07c", size = 198842, upload-time = "2026-06-20T23:49:18.504Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/e88313f16a99930b899ef970d91c281544a470749a359decad994483bbda/wrapt-2.2.2-cp314-cp314t-win32.whl", hash = "sha256:d8a15813215f33fa83667bfc978b300e35669ea8bb424e970a1426bcb7bc6cca", size = 79059, upload-time = "2026-06-20T23:49:20.107Z" }, + { url = "https://files.pythonhosted.org/packages/a0/4f/ac12fda57a55068a094ec42851fb0a40e8489d8941863d517452de62e507/wrapt-2.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d09db0f7e8357060d3c38fc22a018aba683a796bf184360fd1a58f6fc180dc77", size = 82462, upload-time = "2026-06-20T23:49:21.631Z" }, + { url = "https://files.pythonhosted.org/packages/48/a7/df732dac86d9b2027c56bd163dbc883e037b16c3469614752e148d219c61/wrapt-2.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:f32fe639c39561ccc187bcae17e9271be0eb45f1c2952510d2f29b33ab577347", size = 81182, upload-time = "2026-06-20T23:49:23.199Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d2/6317eb6d4554855bbf12d61857774af34747bf88a42c19bf306de67e2fa3/wrapt-2.2.2-py3-none-any.whl", hash = "sha256:5bad217350f19ce99ca5b5e71d406765ea86fe541628426772b657375ee1c048", size = 61460, upload-time = "2026-06-20T23:49:42.966Z" }, +] + +[[package]] +name = "xxhash" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/d4/640915f28a551050e299a2ba6194875de7bfe7e0ecd1be79eb429fcb8a74/xxhash-3.7.1.tar.gz", hash = "sha256:9de50caa75baeca63bcb3b0eb753508a5cddc7757682444d650684bc4ebe1095", size = 82993, upload-time = "2026-06-24T08:21:24.065Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/ae/2d1a44b26968a8de810ad60d1e75ceea0c360b59af9040adbd07da6dbdee/xxhash-3.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a548acf955337291a8bfcf8a648adb4a339e58a20289c27d0ed74aecf71a8ed6", size = 33718, upload-time = "2026-06-24T08:17:05.515Z" }, + { url = "https://files.pythonhosted.org/packages/2f/62/65bb4c406c42ba49a345a70270dc8cdf9671d275c78060a3daa16635158a/xxhash-3.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc808dfa211c9abc8cb0d30308ab3fdbf88a11e9fa526fe67b59fcd144c3369e", size = 31082, upload-time = "2026-06-24T08:17:06.609Z" }, + { url = "https://files.pythonhosted.org/packages/5a/bd/a798f0c43c76c2adef6d8f19f4e98981f4c4024807da26b79d022afe269c/xxhash-3.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0ffe91c645a84154a98d2700475d87977b143a6790f7fb64556e29cb076bec32", size = 195104, upload-time = "2026-06-24T08:17:07.89Z" }, + { url = "https://files.pythonhosted.org/packages/7c/86/30914509b4943da5a7be8c11e9cb8b145abcb492d3b4de57a00de33cc368/xxhash-3.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:43d7a5abf13c22377ce4ebc995ec7452c1a69e1903114438775afbeb00bb4e48", size = 213994, upload-time = "2026-06-24T08:17:09.246Z" }, + { url = "https://files.pythonhosted.org/packages/73/b7/186ce246a374e10aa616a32e661db73b03d41a4d9eedcc6f9cdccf1f8836/xxhash-3.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cd5216faf84dcc46b0110e47bf78ea80fc1ad113862454a67ea46b62286126a9", size = 236947, upload-time = "2026-06-24T08:17:10.67Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d7/ced1473c28c07dade11944af6cb81d924750a41d667bfcfd5d22b3d8e555/xxhash-3.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cf78e23d37f5a546961a2d7a90708c48f331ca9e64149324dba5168f46ecae4b", size = 212700, upload-time = "2026-06-24T08:17:12.043Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ba/23edcea3e24a4ca5ec024154073be7b6b5f1592546ab0784e87aa58c8b6a/xxhash-3.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8160907bb7847e126803d74ea3b7c90aeb275416b2fe2563ac5c6be8733113d0", size = 446211, upload-time = "2026-06-24T08:17:13.556Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ff/6bcb62ba7a12f5db21bec1d963001949d926963fbe1e0b1fc057c6cedc8d/xxhash-3.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:078b5e52184c5ac91e63c42bce205f3c84b8231f11a40ebdee3cb8a4913a7644", size = 194259, upload-time = "2026-06-24T08:17:15.213Z" }, + { url = "https://files.pythonhosted.org/packages/d4/3b/24fffe8b61a38e49a4994f7fa8543a6ea54f01845e222da8a35719995024/xxhash-3.7.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1fb8f77d5ae1db88a7f28441b44d01a3dba6e4850e772c41b158c635d4650d9f", size = 285536, upload-time = "2026-06-24T08:17:16.597Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b0/432b7080f395c3c89538d523cf324e83e252ae37a3eceb4d844c1d7e5c48/xxhash-3.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ed8cc9f7b72bc14591fd639e0ae98bc22f4b217fcd9ab96bf706386c7605964b", size = 211235, upload-time = "2026-06-24T08:17:17.941Z" }, + { url = "https://files.pythonhosted.org/packages/bb/38/94549be8aba0f23b04fd7aa971659d5dcecdde585b66d18c3134349077fe/xxhash-3.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0f359bfb4ecd07be3f51a50e4b9feb4f37722f5c0ce690a4e194db4a333b5033", size = 242216, upload-time = "2026-06-24T08:17:19.401Z" }, + { url = "https://files.pythonhosted.org/packages/c7/77/7b86daf734105faeffbf4454ddca83572c8fedd9e790953685b1d3db646c/xxhash-3.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6b62fe766d1a573bbd1a6d8d3958c01f69a7465bd686452487fa9fda4d3d48b5", size = 199096, upload-time = "2026-06-24T08:17:21.086Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e5/4f4351beb10d3d9262b0998df89b7e8e83e8567305ee5672cc040b640200/xxhash-3.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5dccf79f20eb4416cbd8194c86487bc8f2bc8ed6910299e54379b359a1173c8b", size = 211169, upload-time = "2026-06-24T08:17:22.512Z" }, + { url = "https://files.pythonhosted.org/packages/08/3a/72c7bd3656b517f810db2b251758f75648630447887a1626d807638813b6/xxhash-3.7.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f1423ed4e840f31a52bddd6b9f65f16920f69387b1241c1fb902e10b4d3dfe76", size = 276206, upload-time = "2026-06-24T08:17:23.9Z" }, + { url = "https://files.pythonhosted.org/packages/90/fc/4e9cc8fb3ef6f2aca7c9a9a2294b89733209884ac6326a2d88459fe2d1f8/xxhash-3.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:be9b4a7e27a507535eea4c85adf3c71befdcfad81030a221062edb7f4b989bd0", size = 414422, upload-time = "2026-06-24T08:17:26.127Z" }, + { url = "https://files.pythonhosted.org/packages/32/57/3c39655245cbdcb070444e569ea8d78c4946ba2baf9e9a6842fb43a24284/xxhash-3.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:265495f9de9c448658a0382dfa3bd2d777416096de9e4734797034150e7f347c", size = 191783, upload-time = "2026-06-24T08:17:27.751Z" }, + { url = "https://files.pythonhosted.org/packages/38/16/553f41229cf074c2ae2e66b84d9de35a39d75d83fa06abbdafbcd9fa5f35/xxhash-3.7.1-cp311-cp311-win32.whl", hash = "sha256:c4f1b1de45362c0a2e0a196498d97d95d6f04a50e7435a047cf2924f53248183", size = 31001, upload-time = "2026-06-24T08:17:29.252Z" }, + { url = "https://files.pythonhosted.org/packages/7d/96/1ec33621acf3aec83d252697ad9968c753d986a98c89d37d28169b01956e/xxhash-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:e5f40e8bb83f58d05997f5c7c11651dc369be09b881eaca4c98b2a70c60e9804", size = 31831, upload-time = "2026-06-24T08:17:30.743Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f4/559ce98d3386d267169e9c0a13dad51a03d9140768fd00186ca75921fd0f/xxhash-3.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:60055a8fc14a4c0688eaac8c77dc2b1cc9ab5c4c80ce4398a1d9c350b7ffe05e", size = 28119, upload-time = "2026-06-24T08:17:31.96Z" }, + { url = "https://files.pythonhosted.org/packages/0f/0b/2fae11b5a51a1e713be151f709cca63bcfc3039804a972e242e48ebfebdf/xxhash-3.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a1adf2649445fba8ae80ebe6777b4a92ea5c6c866ea37fa426e22474eb9d5bf2", size = 33651, upload-time = "2026-06-24T08:17:33.054Z" }, + { url = "https://files.pythonhosted.org/packages/c7/cf/ff8cce70213e64618875ed1ccacba32ec3484f95c7949bf69bd534e0eadc/xxhash-3.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40241adc0e7f48990ccb1604e8a61c8fe2fd29d6840fb3c24f29a02b58e293e3", size = 31108, upload-time = "2026-06-24T08:17:34.423Z" }, + { url = "https://files.pythonhosted.org/packages/cf/47/45b9437d3759b3db58d7a57a60590d82c7299f92c9ed4b968e9f207ef354/xxhash-3.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8ba8526a6eee3e36eab5f084e509bb672ba10f127e92b732b21643993672be93", size = 194488, upload-time = "2026-06-24T08:17:35.949Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e7/8a56baba72e426643fc07ab0408ecb25f6f338e228c99b309853e12212b2/xxhash-3.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef90eeda11c3c72ee10749550b44016a4ef31812d747f05a63d347af31198202", size = 213345, upload-time = "2026-06-24T08:17:37.724Z" }, + { url = "https://files.pythonhosted.org/packages/19/2c/51e5ad9923d6f153a907ee038870e4e32c4cf2abc7f7fc46fa97683d5a8e/xxhash-3.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:04f5789e424d896599f5d2b8c815ef8677606471db61781f69b1c6984cb2d301", size = 236681, upload-time = "2026-06-24T08:17:39.201Z" }, + { url = "https://files.pythonhosted.org/packages/c3/36/c2a9d450ffc82fef5f4398b569b6929eab39015aa20811f2133cce2763e6/xxhash-3.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:93feec0ab868dbace1bc0359718b6212818103d6d92e18cc283032ebc05d766d", size = 212456, upload-time = "2026-06-24T08:17:41.129Z" }, + { url = "https://files.pythonhosted.org/packages/18/2f/773cc4821661db54212bee593d5dc0ce829d56a3b1f7a299503da706c702/xxhash-3.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6169425427cdc735ade703873c433bc8ced7f7b8acbc149fbfc5232ae8afd66c", size = 445829, upload-time = "2026-06-24T08:17:42.805Z" }, + { url = "https://files.pythonhosted.org/packages/2f/84/afca49532c9f40b35bdfac76c15edc038e8526e239e6712aa033229601d3/xxhash-3.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54d1b92b2d1b462704191692f99f820e2f74434bc29e13f2eb845516d4345008", size = 194273, upload-time = "2026-06-24T08:17:44.26Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9a/02b14faa4ab626c64e4c5555e8e35ae7979586f4d1f5fe4cc29ba18e51ce/xxhash-3.7.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f3e5eea0c6a387edd80fc4c08d88d3f8c73d33dd614bf9e8d0ac3fd1eed6c4fe", size = 285168, upload-time = "2026-06-24T08:17:45.707Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2f/3ec42637069ad05938c2b667bbcd26443492d30c70622c45e1c093c792e3/xxhash-3.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4b98be721a75741f570c10117190e6ba353981d9ec339ca33ac891e9481622f", size = 210781, upload-time = "2026-06-24T08:17:47.411Z" }, + { url = "https://files.pythonhosted.org/packages/ea/7a/911b2c6ea96af6fd01022c3b5feb7146acd68d228d1a23a2d1f5aa77b9e1/xxhash-3.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75c6ba10f94209ee2087ea13344a4816fe85d349d8ef4c6e55e0fb7b2193f2df", size = 241502, upload-time = "2026-06-24T08:17:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/6f819204a51f6fad03c7d71f1fab5f7721018c2df7b0bd88abc93585f04d/xxhash-3.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3519176963dd11825e92f283b31dac566bf3cbb6b59e2372037170f9a7611643", size = 198415, upload-time = "2026-06-24T08:17:50.682Z" }, + { url = "https://files.pythonhosted.org/packages/be/55/45c547b6c4eb25bdbd07ef33a9bdbe0a2d1ea50210e471cbbc99b7f763e6/xxhash-3.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:797e4a780f6eae0abf99e1519b42ea5cd93d031004c80fb49385d4a210b47f68", size = 210976, upload-time = "2026-06-24T08:17:52.26Z" }, + { url = "https://files.pythonhosted.org/packages/dd/95/f04b07674041734aef5d177e9960354723a9f6b1cc409f749af716df8b7e/xxhash-3.7.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b13072d4b2f8cbe208908c38ad454e16a0a2323bae7b9698c89c33bdb93da673", size = 275718, upload-time = "2026-06-24T08:17:54.131Z" }, + { url = "https://files.pythonhosted.org/packages/08/57/373fa329b9b1119efc2b42b85ed4b2c1fc7bd0882154f52b55d8c1c61028/xxhash-3.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:48e1af1959ec0e5bcc65eff8e66e47e27d2d65d69a0b2d1e0b7023c70cb52578", size = 414477, upload-time = "2026-06-24T08:17:56.088Z" }, + { url = "https://files.pythonhosted.org/packages/4f/62/0dfb6a2a5ed7b12827135cb50f029041748a3b930c243068ae9a93880928/xxhash-3.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d51b0b339d8ec1615e797d16771fbec541fd23cc4ee4af110af55d8116255e40", size = 191815, upload-time = "2026-06-24T08:17:57.718Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/7bc1d0464e04442eae256f33ee87656ae92a0554f91611e456393277d8cc/xxhash-3.7.1-cp312-cp312-win32.whl", hash = "sha256:3c59a4e0dea8ff1ae7fc1169504a82d61334c82896e95be8fafaa865e78b3a06", size = 31054, upload-time = "2026-06-24T08:17:59.263Z" }, + { url = "https://files.pythonhosted.org/packages/7a/99/77004b3e68e4eeb64d3f13c93b33b3fecdb5f8bf94b9140d95c0b24ee93a/xxhash-3.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:751ad2244e4b0dcc719143a9b52efd6d4ab3a6e0531de9aa422a542ef078e38e", size = 31876, upload-time = "2026-06-24T08:18:00.477Z" }, + { url = "https://files.pythonhosted.org/packages/70/fa/af63f28ba11b6d1c6e7646449e7eae6a7bd5c9f958a6d4f82b582fa1b03f/xxhash-3.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:72ef63bdd9806277218471b145298f93f942b8710cad8644d039bb8637317412", size = 28129, upload-time = "2026-06-24T08:18:01.602Z" }, + { url = "https://files.pythonhosted.org/packages/4c/1b/c439778b529a437d9a0cfebe12b8bf4145a32a282b4bf18dee920b9accc2/xxhash-3.7.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:4db20366b104ef337f56820abed761712ba6c5260db31d6422b5aa3cbefa50e3", size = 37089, upload-time = "2026-06-24T08:18:02.915Z" }, + { url = "https://files.pythonhosted.org/packages/58/41/a02809318b408c9fccf0c2d683a748b5150b057955f18d64d6777d5f09de/xxhash-3.7.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:0383bc26f83de80fdd7a9d675ff2daf08b4d7716c520268098bfbeb6dfeede14", size = 35331, upload-time = "2026-06-24T08:18:04.216Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c5/a52eb0b76b31e7132f18d7ef04edaf4ab533d35bd879f2f9123b92fab969/xxhash-3.7.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:af94492aa899eb87e6437c3618be68b8df1d995d22a2712f6c44f4d2397444ad", size = 29867, upload-time = "2026-06-24T08:18:05.346Z" }, + { url = "https://files.pythonhosted.org/packages/4c/73/aacb1f5168081e6ee80165f8a313e0b62fa92c9be9459b137a80775f57dc/xxhash-3.7.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:cd5a296f72edf5aabe58085fa86a693b7b31f06bd1515a44b310b884e61a617f", size = 30881, upload-time = "2026-06-24T08:18:06.521Z" }, + { url = "https://files.pythonhosted.org/packages/17/9f/250e1754686b6bfa700b8eb27af4f1608c0936577f9e400d2a3621a04170/xxhash-3.7.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a77e6224808ce9e834e89d1d0e75e7b959fde7fff7f34c53cac4ba88094c6cc8", size = 33622, upload-time = "2026-06-24T08:18:07.734Z" }, + { url = "https://files.pythonhosted.org/packages/59/61/924a174bcca4cf83e5045ddb5ed7d12980f31a37bffa4660e8dcd71359ba/xxhash-3.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c81c290353c80431b27b0d756db527e882ad086e6c96606ffd57003510a2abbc", size = 33654, upload-time = "2026-06-24T08:18:08.87Z" }, + { url = "https://files.pythonhosted.org/packages/91/a2/23d37b03fa3ca2610067a4fa5c7dd8b464e1c24f4674a2471e313b0a5bb9/xxhash-3.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8ec6c8c01edfc0dde6dcae655951cac7f328920e6d8516e085d335093792937", size = 31109, upload-time = "2026-06-24T08:18:10.381Z" }, + { url = "https://files.pythonhosted.org/packages/b9/aa/cb383b010f7eda6c7456c54f403940b16cd02fe7e23cb2575e58d89a85b3/xxhash-3.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52843733c427ebdd6da8397f69478d013fca0ce4ae990fc5b823f8bc17416c50", size = 194556, upload-time = "2026-06-24T08:18:11.712Z" }, + { url = "https://files.pythonhosted.org/packages/35/7a/117292f36f5b12191e95551e74a33f02719393428eabd1480f0eb382162f/xxhash-3.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:862ba3f1dc1fd5fc6d0952277f4e9953c31abb6f703bf80775095b0dcd4e64e8", size = 213395, upload-time = "2026-06-24T08:18:13.534Z" }, + { url = "https://files.pythonhosted.org/packages/3a/28/9e14ccef87d5ad3b5b39e72183eb4dff129a0a07ed5177f1b79a80ccf76f/xxhash-3.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:33446d6f3cab84c184bffa7575f16a3377ba4249c003b2ab3a667a986f76e314", size = 236454, upload-time = "2026-06-24T08:18:15.615Z" }, + { url = "https://files.pythonhosted.org/packages/18/10/f121f3a40428f5fb2b2c85d4005fd1d82387589d44e7466b0c6538cf3a7f/xxhash-3.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00b51129f16517d1cb16b19a3171905f047464e8a3ce0d978ef38cdb7cbd742e", size = 212512, upload-time = "2026-06-24T08:18:17.23Z" }, + { url = "https://files.pythonhosted.org/packages/ed/3c/26aa2e7cf89c2ceebb5344bfb0df52bb8498657aa22e6d527cc03d22b049/xxhash-3.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb222c844962d50b5b3985e3e4d67d63de96bf40a03f603300580da09c8a826c", size = 445900, upload-time = "2026-06-24T08:18:18.97Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/ab9152424b6e1bbaf2155377c42018fbe6806d8971773e2be05a67fa3139/xxhash-3.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b827658b9a7f43158d9950f736e57ae1c18280294098264bdec82248d79cb29b", size = 194302, upload-time = "2026-06-24T08:18:20.522Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6a/2462e9a7cf46ceb2f12a6def03ff759627c286e99e37efafd21f48ba7049/xxhash-3.7.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aab1f506d3d2b29ba62eac451033e3f8330f4e54e74e7cf300f63152878ae880", size = 285212, upload-time = "2026-06-24T08:18:22.22Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/173838bccab8900a680aa61911faa34d2a52e5de52a38ff853781c4e98d9/xxhash-3.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:364bfdb6a2a1d477ee1b06ac12c656037a666b869b15cbde1eada97e9562e612", size = 210815, upload-time = "2026-06-24T08:18:23.783Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/f38c82fd64c813028c3ae2aca93c62466ef645995a9d42d9d10b38f2725d/xxhash-3.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9f21df182b39a02b727e579eaea524331b48f3f2701fb8df69bc94b7f166c878", size = 241563, upload-time = "2026-06-24T08:18:25.687Z" }, + { url = "https://files.pythonhosted.org/packages/0c/26/60a33f86917ba48584978d0709777c2fdd6fb541b5783c05f8e9949889fd/xxhash-3.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bdf977b8dfa2586f42d6711d15a6d38c7adbd57ba4861151807bccbef67ca911", size = 198483, upload-time = "2026-06-24T08:18:27.352Z" }, + { url = "https://files.pythonhosted.org/packages/e1/03/5a4fabef8b4b3d85fcfefd808f833f56fd541ff9c48df4839136066a6612/xxhash-3.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdb7b3182b3812f992ff83f51bd424eb933dbc1a8a46f1d03176fcba3ae045f2", size = 211005, upload-time = "2026-06-24T08:18:29.262Z" }, + { url = "https://files.pythonhosted.org/packages/df/9d/565af3aff6fd409f33bfa0e747a10a1dac1babb98ede8d2a5a00ebc5a7a9/xxhash-3.7.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:7648d4ce61e6cbcd340cd5d69a5f56f95b0d0e93a09dcc822d97092bef7b9d3c", size = 275768, upload-time = "2026-06-24T08:18:30.672Z" }, + { url = "https://files.pythonhosted.org/packages/5b/2e/474816c84712920ad5c6a592f4837d78250bfa1762c6d5e9bb9ad00252ff/xxhash-3.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a88d6f65bbb5429db37181bab8268003512655f534bb01420a8b3295f8923d55", size = 414540, upload-time = "2026-06-24T08:18:32.628Z" }, + { url = "https://files.pythonhosted.org/packages/22/cb/c6e86f20c68538c960736fa26fc4c01f45619232229d06781d17d19fa55a/xxhash-3.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2e0c4481e42921250eb6b709b12975f2f57bce5af359d8f603860faec67307e5", size = 191854, upload-time = "2026-06-24T08:18:34.412Z" }, + { url = "https://files.pythonhosted.org/packages/a7/1d/3a1b9dc8ceb6ef5b083c3cf4bc6db6297795e001531cde901d706c711e0f/xxhash-3.7.1-cp313-cp313-win32.whl", hash = "sha256:66225f0dcd672b5d547e00bc57a16debde48fded3cff45d7102c9fc3d809b086", size = 31057, upload-time = "2026-06-24T08:18:36.865Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/6f05319a2d7d3587b8250d8dccbac11ef5b565931a6aaae5e83a764ceaa3/xxhash-3.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:f96b3bba2c255072e2c5e8cf054c164e3b6b9263dccd5cd1291e5d595ae6cfa5", size = 31882, upload-time = "2026-06-24T08:18:38.233Z" }, + { url = "https://files.pythonhosted.org/packages/92/72/b80d1f22a0e3d0db0b79fb7923cd237f39bfdbced9e1a513e1271dff48ba/xxhash-3.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:9cc83af5306edd49095d037be0770ca5274ff92d56e5f7cbf7c59e65911d68f5", size = 28133, upload-time = "2026-06-24T08:18:39.627Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ad/60fa52b034447e698881374c828ea9a4ea18a4b8b7b36f9eb6ce8fd49fdd/xxhash-3.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cf37e9ffcdc29b07677e917c3674da8db59f45a06d11121a94961e0b69fe2d1e", size = 33848, upload-time = "2026-06-24T08:18:40.796Z" }, + { url = "https://files.pythonhosted.org/packages/41/f8/24ea82f0f0b2c3a9d40340dba69b144f2827da231f88f3485f33effbddfe/xxhash-3.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdfd9d39b6ff62adaad64a72a54123aff09b8806146db16848c4e6561b9de48a", size = 31346, upload-time = "2026-06-24T08:18:42.218Z" }, + { url = "https://files.pythonhosted.org/packages/5f/84/b6fbca0911375db1f1d835385fd3a31d26751e7aa2b219187fb84af44fed/xxhash-3.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8f23cb81bfd88f3fe7d933376bfb45f4fc0c595021a54628860af805046e0eb6", size = 196894, upload-time = "2026-06-24T08:18:43.514Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ab/5acab22b45492bb316e9b5016d222f0a65083fa4e0f1b876871bde58431d/xxhash-3.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a86bf6437021cfb110e613910d4cb7ed62f44d2d8ae04d91b14600099d7e3ebb", size = 216106, upload-time = "2026-06-24T08:18:44.876Z" }, + { url = "https://files.pythonhosted.org/packages/5e/56/9b9f225f4d0f850870e5520c45f61627d4889c7db5280a489086c0f8a3cf/xxhash-3.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d2bcaac2714a1e90f615cffd1638b7012bf3de3a9c9629f2fb66e025ea7fd00b", size = 238307, upload-time = "2026-06-24T08:18:46.616Z" }, + { url = "https://files.pythonhosted.org/packages/f5/0e/18e011e692677753bb7b2e2ea0b17bf26f0c4efab593147727e53fe11f97/xxhash-3.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8abf100453e8fdaef32a24eba235300ed2a1ca0f41f505459f824a0e934e120", size = 215103, upload-time = "2026-06-24T08:18:48.296Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ec/6d1d0f97a9f41839bae275ad38a144f27e8dd9f507f38aef9fcc93b734a7/xxhash-3.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05b16b82533fbd45376e01a506bbeb1121375448c8b941ae6dc5a879b1c9fc6a", size = 448775, upload-time = "2026-06-24T08:18:50.12Z" }, + { url = "https://files.pythonhosted.org/packages/03/5c/51fbb3071c0e9e8c6835dc35c7f6f0a2e6dbd63703b39760e0c53ac8a111/xxhash-3.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4335a49585aebd2a6755e5f2b5049878095356477e2de7e68fe5b1bedccbedf", size = 196412, upload-time = "2026-06-24T08:18:52.067Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/e3c6fe30de88c848b302bdf2ac254de24a2df2161d7167fe67db3561d70f/xxhash-3.7.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d33231b08db1da77e2199e56e6cf11949a45884999de7b954d80229724aa0f92", size = 287213, upload-time = "2026-06-24T08:18:53.543Z" }, + { url = "https://files.pythonhosted.org/packages/8c/aa/cfde2f35de71992e4fd03b0356f476353a04a6e2b99a5795a26f43d22063/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c3fde1048f949493b2081c62a6516f33da018984a63165c90146ab102f93b900", size = 213346, upload-time = "2026-06-24T08:18:54.992Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5f/cbbefa93e55532fafb12b43d649ebdfbe5567ac5978099db86e1882a4649/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:1bfce305b29b36604a1b0ce7796872cffe5365f41500f01cddf945ce50a2038c", size = 243921, upload-time = "2026-06-24T08:18:56.669Z" }, + { url = "https://files.pythonhosted.org/packages/7e/98/33a7d7f613d4611386e7be8772d67a2d5cbecf863a1b2d9cc6009e26b8f3/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0a0ea48d1e9aa724bb21d9ed5b083420eb8edbe0c8a0d02f7a29bb70bdddba57", size = 201285, upload-time = "2026-06-24T08:18:58.414Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e3/fc0f914932871822d860aaceb5e1bd66bd3795b69ae2c212c0368b862379/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b09310fe99ac2070b65b0eff03d610a67f89793fedcd7c5b48688c34430254cc", size = 213584, upload-time = "2026-06-24T08:18:59.9Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/952fe6f6644e0938c3d18a29bc1560b5a3eaaf5f69c0340df1f028eeff99/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1c32cc0566e852be920ef226a925da2ccccc826c5b1b1bc707373dfd76a459a2", size = 278078, upload-time = "2026-06-24T08:19:01.83Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b5/bbb1d2d46f514ad87d05a4df71ff9f718277d47e9896d48883195ba5b080/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4601e38c2750a744b985dc8245b00eba29ac4619c4c3b29818abfca4be73c1ff", size = 417279, upload-time = "2026-06-24T08:19:03.757Z" }, + { url = "https://files.pythonhosted.org/packages/b6/7f/0949a73192ab6a62a1067498a89c4d70786f8eda75c2ff67559d4cfd4717/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a89cfe5077008f3713c1e2bbf508b3adfc638d4ae7209220e021192daa7e31c2", size = 194176, upload-time = "2026-06-24T08:19:05.298Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/8c62c8291068e58455ce462035f36f007676819e675d818b8d5cc230b44e/xxhash-3.7.1-cp313-cp313t-win32.whl", hash = "sha256:199710e41e0921e4e696635b1b29deca9739ef5961190e793691d3a8b8d1716e", size = 31341, upload-time = "2026-06-24T08:19:06.727Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/c37bed241ee9a96333102f2f439fdd8aa37750a9622518fe4881014c073f/xxhash-3.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:500c5d9b69d43c0acf71d29ce82a0ca9048aea64d2fe6e38af7b53caec7ecc72", size = 32144, upload-time = "2026-06-24T08:19:07.952Z" }, + { url = "https://files.pythonhosted.org/packages/c1/19/97ac96e8238cabade19580caac10ca6cd8ad21111459f4fee2791d63675c/xxhash-3.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:b5af5023fa6a6cfc9fd0f36813cec1e859ba021157e6ebacd8b8900011fd939c", size = 28293, upload-time = "2026-06-24T08:19:09.289Z" }, + { url = "https://files.pythonhosted.org/packages/f0/68/1010395c711cdf676d1eea08909c57e519b41c549b70511e8192161db0f7/xxhash-3.7.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:f56347991b9b40402ac50acb1bef31c31c4ae0b33a5588f9e093b30ccf99d2a2", size = 36891, upload-time = "2026-06-24T08:19:10.465Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d0/500655d067d53b156b02e15faaf3389536e2b65d15162c86e0c06733369b/xxhash-3.7.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:09ed80c2f2f7d23b28729cc58ca8bf0565e00f59125c1648e7e5c80d88e47a92", size = 35220, upload-time = "2026-06-24T08:19:11.752Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2d/dd95e9b492084fc590284be4e8271324172e2b64124d7831d5c32925d6d0/xxhash-3.7.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9eee9540b8c360bfe40cc7809b93187877ecfc054d2ef8d213dd5547dab8dd65", size = 29873, upload-time = "2026-06-24T08:19:13.281Z" }, + { url = "https://files.pythonhosted.org/packages/26/60/4b16f199f262c0463db075320ac71a57a5b367823c5b49b9ec381a5e89b4/xxhash-3.7.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9aa27f83a2b585a2b1daaccc40c51036213ebcc1b10a32d164620ebb802fb20a", size = 30893, upload-time = "2026-06-24T08:19:14.555Z" }, + { url = "https://files.pythonhosted.org/packages/88/d9/538be2550d2af6c43ef3768a7310b99a4c2cb63164017f430b0b9ee4b6c6/xxhash-3.7.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:b831aec61952cec525f12c09b260389e39d811a36b923f734c6e50ffde77bb20", size = 33621, upload-time = "2026-06-24T08:19:15.732Z" }, + { url = "https://files.pythonhosted.org/packages/c6/74/e22ea27aa664f256df508cbd445371ba51b90120e00f35c16d22d35585d8/xxhash-3.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:dea90966f1aee53743b049f9cfd5ff1993e5ef89e56fc8f6bd00e96436b2c6ec", size = 33758, upload-time = "2026-06-24T08:19:16.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/39/1b85d17393d6c592fb80487562e2f248603c447da3627507ffb52ad5748b/xxhash-3.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05d36de6fce47315dd97e572ab3e9a28b0c511ff5b7208b7d5317587a0a336a7", size = 31120, upload-time = "2026-06-24T08:19:18.109Z" }, + { url = "https://files.pythonhosted.org/packages/c2/0f/236c32562c62c7e5d0b21684cf8d1c0ed55122dbce10ad299727c4c9f21c/xxhash-3.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0e1ebafe872c8d787d1b392effccb87ca0fb812829037da94bc48807dbf79e1", size = 194750, upload-time = "2026-06-24T08:19:19.39Z" }, + { url = "https://files.pythonhosted.org/packages/eb/64/811bbf8763edee40b83959ff04112d5f131bb49911073bf39427196f06b9/xxhash-3.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d5ed80d41b056c9d0d2be2a0cd8d0d22462be6f2143bb59068426da0b509613", size = 213507, upload-time = "2026-06-24T08:19:20.857Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a7/8a0d21204e116bf3d494266cb822ad94a99552479b0060b5745453fa0b28/xxhash-3.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:842950f1860bc55a735011e48765567912502cbbb04033ff0efc06ec5842dd16", size = 236592, upload-time = "2026-06-24T08:19:22.29Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/ad5fadb89766120a1393a434d770e14f94dc7ea146e49188910faad83ecf/xxhash-3.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bc500efde92a813436b7fdbbffd51b7320f71aff1cb09d44ac76d31a28ddc5d6", size = 212676, upload-time = "2026-06-24T08:19:23.652Z" }, + { url = "https://files.pythonhosted.org/packages/53/15/99ca84d43b265d48fe5ce5beff02cd9fed5470d8f1b74b3cbf0e43f1e176/xxhash-3.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9b6a61304b66cfb8667f824a31fe26999c936aa50897034fa9b65adcef088c5f", size = 446020, upload-time = "2026-06-24T08:19:25.129Z" }, + { url = "https://files.pythonhosted.org/packages/1e/b6/95e2756867949798386ae5205560495c0243a78cb2a750ffaf069c6a6c34/xxhash-3.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f9a6863d0472c0298e87d6da7ab8c9c49e7072bd7a9303ae04291a9600b6ac2", size = 194387, upload-time = "2026-06-24T08:19:26.746Z" }, + { url = "https://files.pythonhosted.org/packages/54/4a/0c8e3fb9b054a97f37097e163c448266f2f7eaf8ff9f566cd722704c2357/xxhash-3.7.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c5581325e442394a97d996fae3456e0ab9ffc57c1d78d16f7e499858e1de636", size = 285299, upload-time = "2026-06-24T08:19:28.443Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8e/f55f741dadc9b6c6cc8569242683813ffa9c54ef39dd0e769cf937c05423/xxhash-3.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34731e2d8c607bd1d9697b174f9a08f9ea655274fec51e2d66f16b3d40b8837f", size = 210925, upload-time = "2026-06-24T08:19:30.067Z" }, + { url = "https://files.pythonhosted.org/packages/be/e9/d4aaf1c213336fadd92c816ed1ca1d86bdd9d45e802f637f5e38ea771a99/xxhash-3.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1f05c06e71eab1809f017990ae2bdfc63ec961f2a6a2df6d68e5dcff60c0f39e", size = 241668, upload-time = "2026-06-24T08:19:31.544Z" }, + { url = "https://files.pythonhosted.org/packages/88/88/1c674952eb4265521deb5c6cda47952040e74a0813a7933b3a1e3eae2e7b/xxhash-3.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ee9962b423b06824f08ee4849cf8560b7d630ea67c0b1bc21ded2275f899c04", size = 198768, upload-time = "2026-06-24T08:19:33.065Z" }, + { url = "https://files.pythonhosted.org/packages/a7/de/dd6c1b20f6fd02983b22e0907d31b387cf59b6f0679f3bcc160490990178/xxhash-3.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0cb7322f8635cb69ac771e1bf529e6cbea458d1df84e653b0ba49d045aaff8a5", size = 211108, upload-time = "2026-06-24T08:19:34.526Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6c/06f928a7dff78c83bea8ed154ed4ec91be49a8ed4862cc14011d5f6d0ed4/xxhash-3.7.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:156136c6bf615f74e73ba4ddd4b355e1469408745410ef95c3d33b99c0bc1821", size = 275937, upload-time = "2026-06-24T08:19:36.222Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c8/80a5e87f369ede8c82dc3d45fb64a8bedfd5288f63148bbf9759fa30c7f3/xxhash-3.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:81cccac441335635af32b69ddb1d28400094aee8ccd4c80bacf5da61747f600f", size = 414554, upload-time = "2026-06-24T08:19:37.781Z" }, + { url = "https://files.pythonhosted.org/packages/d5/78/cfa9350dd577ae0d27aac2aa6294ee038fa4ad9f4ef7ff4869516916e583/xxhash-3.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f5007843d543fe17889c0df0b02f82df81e28a21a8a498609ccad2b14d0f74a4", size = 191939, upload-time = "2026-06-24T08:19:39.474Z" }, + { url = "https://files.pythonhosted.org/packages/82/cb/d8439f4e662f1393019871fbd2f1145d66a55717b6cb6f32bb873132ffd3/xxhash-3.7.1-cp314-cp314-win32.whl", hash = "sha256:62be5960d81bc06416871aeab8b26a21ebab4650fa3e51f900aa7b59cc5ab618", size = 31656, upload-time = "2026-06-24T08:19:40.906Z" }, + { url = "https://files.pythonhosted.org/packages/56/b8/948c4367d1de11e29d94981e4b79a18fc805e763c9009d0b36cca43a2154/xxhash-3.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:6cb3008e57b1a837c841d344d802abb133ae80296f39a519031bdb4b7a8b547a", size = 32639, upload-time = "2026-06-24T08:19:42.191Z" }, + { url = "https://files.pythonhosted.org/packages/10/4e/18635110d4c28d4badff26e86860cedaeaf83efe90aeef694eb2792b47a7/xxhash-3.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:e1e84128aca5db8aa402d63385ff878a69409f5097dda22209aab182f94d8823", size = 29049, upload-time = "2026-06-24T08:19:43.715Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d3/eea194b5ecdbc4ddc19d60d0b88eee0e11dbae846b6e4763916ccb8d8b19/xxhash-3.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:86fc508b4109eb496139055573233807bbbc7def84d4585ce8fbad005ed716a9", size = 33920, upload-time = "2026-06-24T08:19:45.383Z" }, + { url = "https://files.pythonhosted.org/packages/68/4d/f33bfbc55eb20c4d0789468135fd1dd62ea0e620dca6f259f2d919f2cc14/xxhash-3.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:673d58460a3596f6a72cc7f1efc387cfcade965e144011af2ce76e03823ab1e7", size = 31347, upload-time = "2026-06-24T08:19:46.59Z" }, + { url = "https://files.pythonhosted.org/packages/66/04/743a84186307f61b21ebcc5256f22cf5c0c7b8f552ceefc53dc866166883/xxhash-3.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c671fd48a49c6e4ac0d5ac785f2f7fb2c458dc8dbb52d1eb933dd66271560e47", size = 197035, upload-time = "2026-06-24T08:19:47.936Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/1d1fa91a193248ae7fde17a898aa28cbffd3a62716acc760b3faf1b8816c/xxhash-3.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d9d72bf4811ad101560a5dc2ed30257584123ca33f8ab56461c0dc7b6e23b9a", size = 216203, upload-time = "2026-06-24T08:19:49.413Z" }, + { url = "https://files.pythonhosted.org/packages/f4/cc/684b585c2fe6177a38f7d9d31ea3e58f1d6a5c8ff569490c67bc158c26c4/xxhash-3.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:05f6d7f9d6a4fc1f135a2c6a2c42757ad1846ddd5d8790dd1e8559679879536c", size = 238379, upload-time = "2026-06-24T08:19:51.136Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/645da0d06e404bc02373f5ac01f8a0b805cc95d482a8c742acd177a33f90/xxhash-3.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f7b2d9dc97ca0ee8c39dc3d1ce643aa4b584ec5999a8255ee7035c02efb69a5", size = 215225, upload-time = "2026-06-24T08:19:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/e3/23/10b269565f7774defabc55e2f4a8611415d89f4f0d8f8346aa3f9bd15f01/xxhash-3.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:47113e60f4bab943e40ca984aa2ee40bb2bade72e079a02ce56e497f215a5a05", size = 448904, upload-time = "2026-06-24T08:19:55.024Z" }, + { url = "https://files.pythonhosted.org/packages/2f/45/fda5848405462389ef3728450782e8454a6cf1646354bbe35789cf9a3ce7/xxhash-3.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd9ddce013e2ebd36e13fe52ead4f76be3cf39a3764299bd9962420fa9c6a458", size = 196504, upload-time = "2026-06-24T08:19:56.582Z" }, + { url = "https://files.pythonhosted.org/packages/04/ed/6df8ae0f16fdc70716ff3689005d141503584e946c5c94d52804a4595e77/xxhash-3.7.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fdcaf46c36ffa40120c63c816f5de2d75c10876af894eaadfd51abebaa5888a7", size = 287360, upload-time = "2026-06-24T08:19:58.411Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d0/2a1dfb2b4c15656ca8913118538c40a7481c689a7118cb6b1351811e2591/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f712ee8ae977fe6e941a6c7d29d2415c2b635b3c0c56f0e0fe745036f5176bc5", size = 213431, upload-time = "2026-06-24T08:20:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c3/21e5ed79eec067fec447d1b36ea405fc87052c49ad05a3e6429d43ba6df9/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:43c35d20015ed2108a0e2a02a941694bb9fde154bb6c1112f8b39d35961ad09e", size = 244089, upload-time = "2026-06-24T08:20:01.747Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0a/91c7c16ab909e68698b6ec9022db6dfcc6814bc6494cb01056a31dfea79a/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:7b67a394f06d499b8525ad98b84f77c3b226cbfba22c35dfcc7425fc7b1f473a", size = 201401, upload-time = "2026-06-24T08:20:03.357Z" }, + { url = "https://files.pythonhosted.org/packages/54/48/e1880f94057f9c3c6998a68eef849154781bb8b527a1320c75ed7050b32d/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:39ab6df3c9d76f477c7b174fc77b6e7fdfeeead6899699539bfb3850494c3d22", size = 213677, upload-time = "2026-06-24T08:20:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c8/baeeb1d4b846fca2e313ee8d65c426dd4560548c5e1354b80666c12abcd4/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7db59b3293c8cd6c2ca9854f1b246502f8dd0b72caa4dc755b85c10594fd427d", size = 278210, upload-time = "2026-06-24T08:20:06.498Z" }, + { url = "https://files.pythonhosted.org/packages/19/b0/d01ab517da006259dc00fc89b32f39acd42492756364417e957371c5bd09/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fd73cebdefbdc6aaccc910041ad192426279ff87a856274d1d6e202ba2546b96", size = 417417, upload-time = "2026-06-24T08:20:08.099Z" }, + { url = "https://files.pythonhosted.org/packages/01/10/582cf987173c4b7e272396ede2694005fc204612acaa5d13e74761f017e1/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7a285830529716693c72a463a4313b4862482730b308afbb1c91a208eab7330d", size = 194291, upload-time = "2026-06-24T08:20:09.805Z" }, + { url = "https://files.pythonhosted.org/packages/3f/df/61beb9e061ddaa349f1a6e75ef14f174af1439de0308d63f2561039eb522/xxhash-3.7.1-cp314-cp314t-win32.whl", hash = "sha256:02ad31001ca4f8241b5edc38f009a189d075f270b023e1a1ba01b1aadf7240a8", size = 32026, upload-time = "2026-06-24T08:20:11.383Z" }, + { url = "https://files.pythonhosted.org/packages/78/b1/c6a5ba0ecba582d9302daf67fdb13a5081b122ea5ce18aa6df3e59001cbe/xxhash-3.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:68ee1b9903252e80437559d32ae6d0a752e6a26fa6416925c7e6282500948838", size = 32887, upload-time = "2026-06-24T08:20:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/c7/88/70b85fdae219f77d8e70035cef3d6527e4d000aa7038ff046279d47aa39a/xxhash-3.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5795037fb23aa33bdb1d6daa6a68b001c4462de447c1a31917ae1aebca691286", size = 29160, upload-time = "2026-06-24T08:20:14.297Z" }, + { url = "https://files.pythonhosted.org/packages/b9/6b/5e244d6550e17c0beb3decdc30a7d87867c4f00df0c00a5c9115d20acd56/xxhash-3.7.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:70debeb180feba224a0ba2bc532e643ea7e94d21147ecb1770124cafcf258aa1", size = 31386, upload-time = "2026-06-24T08:21:16.236Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a6/924b9b6b05a9e345f19cc5890b84bc4843495aa88867e131d9fde42bc454/xxhash-3.7.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76abdff52f009610e80969870c5397e74d5f8d8c511ebb418ad166e788f0614a", size = 28804, upload-time = "2026-06-24T08:21:17.488Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7f/79e7fcab1057a20f2359deef24a5a63ad0395a6dabf3cdc577ab5a310a87/xxhash-3.7.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0666d8933fbf52141864357c0e4c48fdaec6138f4459a2fc0c9fc5b581902b22", size = 41385, upload-time = "2026-06-24T08:21:18.827Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a8/40aa7fee7a0f9a92ec708f5a437a7e4100cb365d60661bcd8bfdc5af60bf/xxhash-3.7.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25451c853781de56b41a6b45160b6d62ab9c313abf97501539a3984599427b40", size = 36582, upload-time = "2026-06-24T08:21:20.117Z" }, + { url = "https://files.pythonhosted.org/packages/64/15/b86254d961ef925bee70aeda0cfe769cc35a81f546bfed47823b5db6df07/xxhash-3.7.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81c5bc7de50c23002577f44bf7719497e54c41ded0da8acf223f1b3823c541b8", size = 32808, upload-time = "2026-06-24T08:21:21.416Z" }, + { url = "https://files.pythonhosted.org/packages/75/3f/9036bc57048a72707bf97bc7632335ad32f6047301035de46d7cabd14e5c/xxhash-3.7.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:da6f19cb2a75fe8417ca03b40c7efdcfeaa0d24b9cae782199498b2cb0a29911", size = 31935, upload-time = "2026-06-24T08:21:22.657Z" }, +] + +[[package]] +name = "yarl" +version = "1.24.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/c5/1ce244152ff2839645e7cae92f90e7bafcb2c52bea7ff586ac714f14f5df/yarl-1.24.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:36348bebb147b83818b9d7e673ea4debc75970afc6ffdc7e3975ad05ce5a58c1", size = 128971, upload-time = "2026-05-19T21:28:20.543Z" }, + { url = "https://files.pythonhosted.org/packages/87/5a/00f36967203ed89cb3acd2c8ed526cc3fed9418eb70ce128160a911c8499/yarl-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a97e42c8a2233f2f279ecadd9e4a037bcb5d813b78435e8eedd4db5a9e9708c", size = 91507, upload-time = "2026-05-19T21:28:22.556Z" }, + { url = "https://files.pythonhosted.org/packages/31/d0/1fb0c1cd27288f39f6974da4318c32768d72c9890984541fdf1e2e32a51d/yarl-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d027d56f1035e339d1001ac33eceab5b2ec8e42e449787bb75e289fb9a5cd1d", size = 91343, upload-time = "2026-05-19T21:28:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/03/ce/d4a646508bed2f8dec6435b40166fe9308dd191262033d3f307b2bbcaecd/yarl-1.24.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a6377060e7927187a42b7eb202090cbe2b34933a4eeaf90e3bd9e33432e5cae", size = 105704, upload-time = "2026-05-19T21:28:25.872Z" }, + { url = "https://files.pythonhosted.org/packages/4b/07/b3278e82d8bc41485bcf6d856cd0433262593de615b1d3dc43bd3f5bead4/yarl-1.24.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:17076578bce0049a5ce57d14ad1bded391b68a3b213e9b81b0097b090244999a", size = 97281, upload-time = "2026-05-19T21:28:27.352Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/4cee6e7c92e487bebe7afc797da0aa54a248ab4e776a68fe369ec29665a5/yarl-1.24.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:50713f1d4d6be6375bb178bb43d140ee1acb8abe589cd723320b7925a275be1e", size = 114020, upload-time = "2026-05-19T21:28:29.458Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/111076571545a7d4f9cca3fbd5c6f40615af58642be09f12328f48022468/yarl-1.24.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:34263e2fa8fb5bb63a0d97706cda38edbad62fddb58c7f12d6acbc092812aa50", size = 111450, upload-time = "2026-05-19T21:28:31.262Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ec/08f671f69a444d704aeecebf92af659b67b97a869942411d0a578b08c334/yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49016d82f032b1bd1e10b01078a7d29ae71bf468eeae0ea22df8bab691e60003", size = 106384, upload-time = "2026-05-19T21:28:32.856Z" }, + { url = "https://files.pythonhosted.org/packages/e5/86/ce41e7a7a199340b2330d52b60f25c4074b6636dd0e60b1a80d31a9db042/yarl-1.24.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3f6d2c216318f8f32038ca3f72501ba08536f0fd18a36e858836b121b2deed9f", size = 106153, upload-time = "2026-05-19T21:28:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5d/31be8a729531ab3e55ac3e7e5c800be8c89ea98947f418b2f6ea259fb6ee/yarl-1.24.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:08d3a33218e0c64393e7610284e770409a9c31c429b078bcb24096ed0a783b8f", size = 105322, upload-time = "2026-05-19T21:28:36.642Z" }, + { url = "https://files.pythonhosted.org/packages/47/9b/b57afb22b386ae87ac9940f09878b98d8c333f89113e6fc96fcf4ca9eb64/yarl-1.24.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5d699376c4ca3cba49bbfae3a05b5b70ded572937171ce1e0b8d87118e2ba294", size = 99057, upload-time = "2026-05-19T21:28:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4f/06348c27c8389256c313e8a57d796808fc0264c915dd5e7cfd3c0e314dc7/yarl-1.24.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a1cab588b4fa14bea2e55ebea27478adfb05372f47573738e1acc4a36c0b05d2", size = 113502, upload-time = "2026-05-19T21:28:40.091Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1c/284f307b298e4a17b7943b07d9d7ecc4151537f8d137ba51f3bb6c31ca20/yarl-1.24.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ec87ccc31bd21db7ad009d8572c127c1000f268517618a4cc09adba3c2a7f21c", size = 105253, upload-time = "2026-05-19T21:28:41.987Z" }, + { url = "https://files.pythonhosted.org/packages/c8/bf/0de123bec8619e45c80cbded9085f61b5b4a9eddb8abe6d25d28ee1ec866/yarl-1.24.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d1dd47a22843b212baa8d74f37796815d43bd046b42a0f41e9da433386c3136b", size = 111345, upload-time = "2026-05-19T21:28:43.93Z" }, + { url = "https://files.pythonhosted.org/packages/90/af/0248eb065e51129d2a9b2436cd1b5c772c19a6b04e5b6a186955671e3319/yarl-1.24.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7b54b9c67c2b06bd7b9a77253d242124b9c95d2c02def5a1144001ee547dd9d5", size = 106558, upload-time = "2026-05-19T21:28:45.806Z" }, + { url = "https://files.pythonhosted.org/packages/21/3c/f960d7a65ef97d8ba9b424fb5128796a4bc710fc6df2ddbbd7dfdc3bbd20/yarl-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:f8fdbcff8b2c7c9284e60c196f693588598ddcee31e11c18e14949ce44519d45", size = 92808, upload-time = "2026-05-19T21:28:48.465Z" }, + { url = "https://files.pythonhosted.org/packages/03/1a/49fb03750e4de4d2284cd5b885a383133c34eef45bd59631b2bb8b7e81e8/yarl-1.24.2-cp311-cp311-win_arm64.whl", hash = "sha256:b32c37a7a337e90822c45797bf3d79d60875cfcccd3ecc80e9f453d87026c122", size = 87610, upload-time = "2026-05-19T21:28:50.07Z" }, + { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" }, + { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" }, + { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" }, + { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" }, + { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" }, + { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" }, + { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" }, + { url = "https://files.pythonhosted.org/packages/a7/25/722e3b93bd687009afb2d59a35e13d30ddd8f80571445bb0c4e4ce26ec66/yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad", size = 92901, upload-time = "2026-05-19T21:29:20.014Z" }, + { url = "https://files.pythonhosted.org/packages/39/47/4486ccfb674c04854a1ef8aa77868b6a6f765feaf69633409d7ca4f02cb8/yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30", size = 87229, upload-time = "2026-05-19T21:29:22.1Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/fcf0ce677f17e5c471c06311dd25964be38a4c586993632910d2e75278bc/yarl-1.24.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491ac9141decf49ee8030199e1ee251cdff0e131f25678817ff6aa5f837a3536", size = 128978, upload-time = "2026-05-19T21:29:23.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/58/8e63299bb71ed61a834121d9d3fe6c9fcf2a6a5d09754ff4f20f2d20baf5/yarl-1.24.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e89418f65eda18f99030386305bd44d7d504e328a7945db1ead514fbe03a0607", size = 91733, upload-time = "2026-05-19T21:29:25.375Z" }, + { url = "https://files.pythonhosted.org/packages/c1/24/16748d5dab6daec8b0ed81ccec639a1cded0f18dcc62a4f696b4fe366c37/yarl-1.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdfcce633b4a4bb8281913c57fcafd4b5933fbc19111a5e3930bbd299d6102f1", size = 91113, upload-time = "2026-05-19T21:29:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/b63fff7b71211e866624b21432d5943cbb633eb0c2872d9ee3070648f22c/yarl-1.24.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863297ddede92ee49024e9a9b11ecb59f310ca85b60d8537f56bed9bbb5b1986", size = 103899, upload-time = "2026-05-19T21:29:28.842Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/ba1974b8533909636f7733fe86cf677e3619527c3c2fa913e0ea89c48757/yarl-1.24.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:374423f70754a2c96942ede36a29d37dc6b0cb8f92f8d009ddf3ed78d3da5488", size = 97862, upload-time = "2026-05-19T21:29:31.086Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/123ac993b5c2ba6f554a140305620cb8f150fa543711bbc49be3ec0a65a4/yarl-1.24.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33a29b5d00ccbf3219bb3e351d7875739c19481e030779f48cc46a7a71681a9b", size = 111060, upload-time = "2026-05-19T21:29:32.657Z" }, + { url = "https://files.pythonhosted.org/packages/23/37/c472d3af3509688392134a88a825276770a187f1daa4de3f6dc0a327a751/yarl-1.24.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9532c57211730c515341af11fef6e9b61d157487272a096d0c04da445642592", size = 110613, upload-time = "2026-05-19T21:29:34.379Z" }, + { url = "https://files.pythonhosted.org/packages/df/88/09c28dad91e662ccfaa1b78f1c57badde74fc9d0b23e74aef644750ecd73/yarl-1.24.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91e72cf093fd833483a97ee648e0c053c7c629f51ff4a0e7edd84f806b0c5617", size = 107012, upload-time = "2026-05-19T21:29:36.216Z" }, + { url = "https://files.pythonhosted.org/packages/07/ab/9d4f69d571a94f4d112fa7e2e007200f5a54d319f58c82ac7b7baa61f5c6/yarl-1.24.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3177bc0a768ef3bacceb4f272632990b7bea352f1b2f1eee9d6d6ff16516f92", size = 105887, upload-time = "2026-05-19T21:29:38.746Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9a/000b2b66c0d772a499fc531d21dab92dfeb73b640a12eed6ba89f49bb2d0/yarl-1.24.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e196952aacaf3b232e265ff02980b64d483dc0972bd49bcb061171ff22ac203a", size = 103620, upload-time = "2026-05-19T21:29:40.368Z" }, + { url = "https://files.pythonhosted.org/packages/41/7c/7c1050f73450fbdaa3f0c72017059f00ce5e13366692f3dba25275a1083d/yarl-1.24.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:204e7a61ce99919c0de1bf904ab5d7aa188a129ea8f690a8f76cfb6e2844dc44", size = 100599, upload-time = "2026-05-19T21:29:42.66Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b1/29e5756b3926705f5f6089bd5b9f50a56eaac550da6e260bf713ead44d04/yarl-1.24.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b156914620f0b9d78dc1adb3751141daee561cfec796088abb89ed49d220f1a", size = 110604, upload-time = "2026-05-19T21:29:44.632Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/8415bc96e9b150cde942fbac9a8182985e58f40ce5c54c34ed015407d3ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8372a2b976cf70654b2be6619ab6068acabb35f724c0fda7b277fbf53d66a5cf", size = 105161, upload-time = "2026-05-19T21:29:46.755Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d4/cde059abfa229553b7298a2eadde2752e723d50aeedaef86ce59da2718ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f9a1e9b622ca284143aab5d885848686dcd85453bb1ca9abcdb7503e64dc0056", size = 110619, upload-time = "2026-05-19T21:29:48.972Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2c/d6a6c9a61549f7b6c7e6dc6937d195bcf069582b47b7200dcd0e7b256acf/yarl-1.24.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:810e19b685c8c3c5862f6a38160a1f4e4c0916c9390024ec347b6157a45a0992", size = 107362, upload-time = "2026-05-19T21:29:51Z" }, + { url = "https://files.pythonhosted.org/packages/92/dd/3ae5fe417e9d1c353a548553326eb9935e76b6b727161563b424cc296df3/yarl-1.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:7d37fb7c38f2b6edab0f845c4f85148d4c44204f52bc127021bd2bc9fdbf1656", size = 92667, upload-time = "2026-05-19T21:29:52.743Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/a7beb239f78f27fca1b053c8e8595e4179c02e62249b4687ec218c370c50/yarl-1.24.2-cp313-cp313-win_arm64.whl", hash = "sha256:1e831894be7c2954240e49791fa4b50c05a0dc881de2552cfe3ffd8631c7f461", size = 87069, upload-time = "2026-05-19T21:29:54.442Z" }, + { url = "https://files.pythonhosted.org/packages/40/0e/e08087695fc12789263821c5dc0f8dc52b5b17efd0887cacf419f8a43ba3/yarl-1.24.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2", size = 129670, upload-time = "2026-05-19T21:29:56.631Z" }, + { url = "https://files.pythonhosted.org/packages/3a/98/ab4b5ed1b1b5cd973c8a3eb994c3a6aefb6ce6d399e21bb5f0316c33815c/yarl-1.24.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630", size = 91916, upload-time = "2026-05-19T21:29:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8", size = 91625, upload-time = "2026-05-19T21:30:00.412Z" }, + { url = "https://files.pythonhosted.org/packages/02/a7/45baabfff76829264e623b185cff0c340d7e11bf3e1cd9ea37e7d17934bd/yarl-1.24.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14", size = 104574, upload-time = "2026-05-19T21:30:02.544Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/3a5ab144d3d650ca37d4f4b57e56169be8af3ca34c448793e064b30baaed/yarl-1.24.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535", size = 97534, upload-time = "2026-05-19T21:30:04.319Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b5/5658fef3681fb5776b4513b052bec750009f47b3a592251c705d75375798/yarl-1.24.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14", size = 111481, upload-time = "2026-05-19T21:30:05.988Z" }, + { url = "https://files.pythonhosted.org/packages/4c/06/fdcd7dde037f00866dce123ed4ba23dba94beb56fc4cf561668d27be37f2/yarl-1.24.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3", size = 111529, upload-time = "2026-05-19T21:30:07.738Z" }, + { url = "https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208", size = 107338, upload-time = "2026-05-19T21:30:09.713Z" }, + { url = "https://files.pythonhosted.org/packages/ae/04/23049463f729bd899df203a7960505a75333edd499cda8aa1d5a82b64df5/yarl-1.24.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50", size = 106147, upload-time = "2026-05-19T21:30:11.365Z" }, + { url = "https://files.pythonhosted.org/packages/14/18/04a4b5830b43ed5e4c5015b40e9f6241ad91487d71611061b4e111d6ac80/yarl-1.24.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd", size = 104272, upload-time = "2026-05-19T21:30:12.978Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f7/8cffdf319aee7a7c1dbd07b61d91c3e3fda460c7a93b5f93e445f3806c4c/yarl-1.24.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67", size = 99962, upload-time = "2026-05-19T21:30:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/b3cce3b7dbef64ac700ad4cea156a207d01bede0f507587616c364b5468e/yarl-1.24.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1", size = 111063, upload-time = "2026-05-19T21:30:16.683Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ea/100818505e7ebf165c7242ff17fdf7d9fee79e27234aeca871c1082920d7/yarl-1.24.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1", size = 105438, upload-time = "2026-05-19T21:30:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d2/e075a0b32aa6625087de9e653087df0759fed5de4a435fef594181102a77/yarl-1.24.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b", size = 111458, upload-time = "2026-05-19T21:30:21.024Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5c/ceea7ba98b65c8eb8d947fdc52f9bedfcd43c6a57c9e3c90c17be8f324a3/yarl-1.24.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8", size = 107589, upload-time = "2026-05-19T21:30:23.412Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl", hash = "sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0", size = 94424, upload-time = "2026-05-19T21:30:25.425Z" }, + { url = "https://files.pythonhosted.org/packages/92/10/7dc07a0e22806a9280f42a57361395506e800c64e22737cd7b0886feab42/yarl-1.24.2-cp314-cp314-win_arm64.whl", hash = "sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57", size = 88690, upload-time = "2026-05-19T21:30:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/9e/13/d5b8e2c8667db955bcb3de233f18798fefe7edf1d7429c2c9d4f9c401114/yarl-1.24.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b", size = 136248, upload-time = "2026-05-19T21:30:29.297Z" }, + { url = "https://files.pythonhosted.org/packages/de/46/a4a97c05c9c9b8fd266bb2a0df12992c7fbd02391eb9640583411b6dab32/yarl-1.24.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761", size = 95084, upload-time = "2026-05-19T21:30:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/95/b2/845cf2074a015e6fe0d0808cf1a2d9e868386c4220d657ebd8302b199043/yarl-1.24.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8", size = 95272, upload-time = "2026-05-19T21:30:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/fe/16/e69d4aa244aef45235ddfebc0e04036a6829842bc5a6a795aedc6c998d23/yarl-1.24.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed", size = 101497, upload-time = "2026-05-19T21:30:34.842Z" }, + { url = "https://files.pythonhosted.org/packages/15/94/c07107715d621076863ee88b3ddf183fa5e9d4aba5769623c9979828410a/yarl-1.24.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543", size = 94002, upload-time = "2026-05-19T21:30:37.724Z" }, + { url = "https://files.pythonhosted.org/packages/a9/35/fc1bbdd895b5e4010b8fdd037f7ed3aa289d3863e08231b30231ca9a0815/yarl-1.24.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0", size = 106524, upload-time = "2026-05-19T21:30:40.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/32b66d0a4ba47c296cf86d03e2c67bff58399fe6d6d84d5205c04c66cc6d/yarl-1.24.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024", size = 106165, upload-time = "2026-05-19T21:30:41.888Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/37cb5ff50c5e825d4d38e81bb04d1b7e96bf960f7ab89f9850b162f3f114/yarl-1.24.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf", size = 103010, upload-time = "2026-05-19T21:30:43.985Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/4597912315096f7bb359e46e13bf8b60994fcbb2db29b804c0902ef4eff5/yarl-1.24.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc", size = 101128, upload-time = "2026-05-19T21:30:46.291Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/c8e86e120521e646013d02a8e3b8884392e28494be8f392366e50d208efc/yarl-1.24.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb", size = 101382, upload-time = "2026-05-19T21:30:48.085Z" }, + { url = "https://files.pythonhosted.org/packages/fa/98/70b229236118f89dbeb739b76f10225bbf53b5497725502594c9a01d699a/yarl-1.24.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420", size = 95964, upload-time = "2026-05-19T21:30:49.785Z" }, + { url = "https://files.pythonhosted.org/packages/87/f8/56c386981e3c8648d279fdef2397ffec577e8320fd5649745e34d54faeb7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f", size = 106204, upload-time = "2026-05-19T21:30:51.862Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1e/765afe97811ca35933e2a7de70ac57b1997ea2e4ee895719ee7a231fb7e5/yarl-1.24.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa", size = 101510, upload-time = "2026-05-19T21:30:53.62Z" }, + { url = "https://files.pythonhosted.org/packages/ee/78/393913f4b9039e1edd09ae8a9bbb9d539be909a8abf6d8a2084585bed4b7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe", size = 105584, upload-time = "2026-05-19T21:30:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/78/87/deb17b7049bbe74ea11a713b86f8f27800cc1c8648b0b797243ebb4830ba/yarl-1.24.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd", size = 103410, upload-time = "2026-05-19T21:30:57.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/be/f9f7594e23b5b93affff0318e4593c1920331bcaefda326cabcad94296a1/yarl-1.24.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215", size = 102980, upload-time = "2026-05-19T21:30:59.735Z" }, + { url = "https://files.pythonhosted.org/packages/65/a4/ba80dccd3593ff1f01051a818694d07b58cb8232677ee9a22a5a1f93a9fc/yarl-1.24.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d", size = 91219, upload-time = "2026-05-19T21:31:01.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" }, +] + +[[package]] +name = "youtube-transcript-api" +version = "1.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "defusedxml" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/43/4104185a2eaa839daa693b30e15c37e7e58795e8e09ec414f22b3db54bec/youtube_transcript_api-1.2.4.tar.gz", hash = "sha256:b72d0e96a335df599d67cee51d49e143cff4f45b84bcafc202ff51291603ddcd", size = 469839, upload-time = "2026-01-29T09:09:17.088Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/95/129ea37efd6cd6ed00f62baae6543345c677810b8a3bf0026756e1d3cf3c/youtube_transcript_api-1.2.4-py3-none-any.whl", hash = "sha256:03878759356da5caf5edac77431780b91448fb3d8c21d4496015bdc8a7bc43ff", size = 485227, upload-time = "2026-01-29T09:09:15.427Z" }, +] + +[[package]] +name = "zstandard" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, + { 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/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, + { 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/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, + { 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/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, + { 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/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, + { 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/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, + { 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/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, + { 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/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, + { 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/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, +] From 9ceab884a43bcdc82d0e189da105dae43b9eb16c Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 00:08:01 -0500 Subject: [PATCH 02/35] ci: rely on default codeql setup --- .github/workflows/codeql.yml | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 69c18a0..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: CodeQL - -on: - push: - branches: [main] - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - schedule: - - cron: "17 6 * * 2" - -permissions: - contents: read - security-events: write - -jobs: - analyze: - name: Analyze Python - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - name: Initialize CodeQL - uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 - with: - languages: python - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 From 3e33c4d8a06bceb6f492155fa085ab910f4fc10e Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 00:20:20 -0500 Subject: [PATCH 03/35] fix: harden tool and config boundaries --- README.md | 10 +- docs/agentic-workflows.rst | 29 ++++ packages/agentic-fabric/README.md | 2 + packages/agentic-fabric/pyproject.toml | 3 - .../src/agentic_fabric/core/discovery.py | 21 ++- .../agentic-fabric/src/agentic_fabric/crew.py | 159 ------------------ .../agentic-fabric/src/agentic_fabric/main.py | 71 ++------ .../src/agentic_fabric/run_flow.py | 128 -------------- .../runners/local_cli_runner.py | 50 +++++- .../src/agentic_fabric/tools/file_tools.py | 59 ++++--- .../src/agentic_fabric/tools/registry.py | 28 +++ .../tests/test_file_tool_paths.py | 50 ++++++ .../tests/test_single_agent_runners.py | 20 +++ .../tests/test_tool_registry.py | 27 ++- .../tests/test_yaml_config_errors.py | 28 +++ 15 files changed, 293 insertions(+), 392 deletions(-) delete mode 100644 packages/agentic-fabric/src/agentic_fabric/crew.py delete mode 100644 packages/agentic-fabric/src/agentic_fabric/run_flow.py create mode 100644 packages/agentic-fabric/tests/test_file_tool_paths.py diff --git a/README.md b/README.md index 95ff2f9..e244d21 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,12 @@ # Agentic Fabric Workspace -This repository is the future public `jbcom/agentic-fabric` workspace. +This repository is the public `jbcom/agentic-fabric` workspace. Documentation: [jonbogaty.com/agentic-fabric](https://jonbogaty.com/agentic-fabric/) Read `AGENTS.md` first in Codex sessions; it contains the active local migration plan and hand-off instructions. -The active branch is `codex/agentic-fabric-bootstrap`. This repository should -be completed after `extended-data` and `vendor-fabric`, because its -`AgenticData` design depends on their final contracts. - -Current implemented surface includes `AgenticData`, lazy runtime registry -metadata, capability decorators, vendor-tool references, and the sibling +The implemented surface includes `AgenticData`, lazy runtime registry metadata, +capability decorators, vendor-tool references, and the sibling `pytest-agentic-fabric` package. diff --git a/docs/agentic-workflows.rst b/docs/agentic-workflows.rst index 253d15c..6318cca 100644 --- a/docs/agentic-workflows.rst +++ b/docs/agentic-workflows.rst @@ -39,3 +39,32 @@ For stateful orchestration, register the same config with When no framework is installed, discovery and metadata inspection still work. Execution reports the unavailable runtime instead of failing during import. + +Tool Resolution +--------------- + +Crew tool entries are resolved lazily. Built-in filesystem tool aliases, +``mcp://filesystem/...`` aliases, and ``vendor://provider/operation`` +references do not import optional framework or vendor packages until the +tool is used. + +For Python tools, prefer registering a factory in application startup: + +.. code:: python + + from agentic_fabric.tools.registry import register_tool_factory + + register_tool_factory("MyTool", lambda: MyTool(), aliases=("my-tool",)) + +Fully qualified ``module:attribute`` and ``package.module.ClassName`` +references are allowed for ``agentic_fabric`` modules by default. External +module imports must be explicitly allowlisted: + +.. code:: bash + + export AGENTIC_FABRIC_TOOL_IMPORT_ALLOWLIST="my_company.tools,shared_agents." + +Manifest paths for ``agents``, ``tasks``, and ``knowledge`` are resolved +relative to the crew config directory and cannot escape it. Filesystem +tools resolve symlinks before reading or writing and keep writes inside +their configured workspace directories. diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index 6fcc248..16b90c1 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -98,6 +98,8 @@ result = session.run_agent("reviewer", runtime="crewai") vendor-layer context together. - Capability decorators: runners and tools expose declared capabilities through read-only metadata and deterministic dispatch. +- Tool resolution: built-in, vendor URI, and registered factories are preferred; + external dynamic imports require `AGENTIC_FABRIC_TOOL_IMPORT_ALLOWLIST`. - YAML-first: crew configuration in YAML, not Python boilerplate. - Hierarchical orchestration: `ManagerAgent` delegates across crews. - Package discovery: finds `.crew/`, `.crewai/`, `.langgraph/`, and diff --git a/packages/agentic-fabric/pyproject.toml b/packages/agentic-fabric/pyproject.toml index 305a663..0253ade 100644 --- a/packages/agentic-fabric/pyproject.toml +++ b/packages/agentic-fabric/pyproject.toml @@ -77,8 +77,6 @@ all = [ [project.scripts] agentic-fabric = "agentic_fabric.main:main" -run-crew = "agentic_fabric.crew:kickoff" -train-crew = "agentic_fabric.crew:train" [project.urls] Documentation = "https://jonbogaty.com/agentic-fabric/" @@ -168,7 +166,6 @@ ignore = [ "examples/**" = ["T201"] "tests/**" = ["B018", "BLE", "D", "DTZ", "INP001", "LOG001", "N802", "N803", "N806", "PLC0415", "PT", "S", "SIM117", "TRY"] "src/agentic_fabric/main.py" = ["T201"] -"src/agentic_fabric/run_flow.py" = ["T201"] [tool.ruff.lint.pydocstyle] convention = "google" diff --git a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py index bc89496..8120dcf 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py @@ -178,6 +178,21 @@ def load_manifest(crewai_dir: Path) -> dict[str, Any]: return result or {} +def _resolve_config_path(config_dir: Path, relative_path: str) -> Path: + """Resolve a manifest path and require it to stay below the config dir.""" + candidate = Path(relative_path) + if candidate.is_absolute() or ".." in candidate.parts: + msg = f"Manifest path must be relative to {config_dir.name}: {relative_path}" + raise ValueError(msg) + + root = config_dir.resolve() + resolved = (root / candidate).resolve(strict=False) + if not resolved.is_relative_to(root): + msg = f"Manifest path escapes config directory: {relative_path}" + raise ValueError(msg) + return resolved + + def get_framework_from_config_dir(config_dir: Path) -> str | None: """Determine the required framework from a config directory path. @@ -215,8 +230,8 @@ def get_crew_config(config_dir: Path, crew_name: str) -> dict: raise ValueError(f"Crew '{crew_name}' not found. Available: {available}") # Load agents and tasks YAML - agents_path = config_dir / crew_config["agents"] - tasks_path = config_dir / crew_config["tasks"] + agents_path = _resolve_config_path(config_dir, crew_config["agents"]) + tasks_path = _resolve_config_path(config_dir, crew_config["tasks"]) agents = yaml.safe_load(agents_path.read_text()) if agents_path.exists() else {} tasks = yaml.safe_load(tasks_path.read_text()) if tasks_path.exists() else {} @@ -224,7 +239,7 @@ def get_crew_config(config_dir: Path, crew_name: str) -> dict: # Resolve knowledge paths knowledge_paths = [] for kp in crew_config.get("knowledge", []): - full_path = config_dir / kp + full_path = _resolve_config_path(config_dir, kp) if full_path.exists(): knowledge_paths.append(full_path) diff --git a/packages/agentic-fabric/src/agentic_fabric/crew.py b/packages/agentic-fabric/src/agentic_fabric/crew.py deleted file mode 100644 index a7e1992..0000000 --- a/packages/agentic-fabric/src/agentic_fabric/crew.py +++ /dev/null @@ -1,159 +0,0 @@ -"""CrewAI Entry Point - -This file is the standard CrewAI entry point that `crewai run` expects. -It loads configuration from crewbase.yaml and executes the crew. -""" - -from __future__ import annotations - -from pathlib import Path -from typing import Any - -import yaml - -from crewai import Crew - - -def get_crewbase_path() -> Path: - """Get path to crewbase.yaml.""" - return Path(__file__).parent.parent.parent / "crewbase.yaml" - - -class CrewAgents: - """Wrapper class for CrewAI crew execution. - - Provides a class-based interface for flows to instantiate and run crews. - This bridges the gap between Flow expectations and the YAML-based crew config. - """ - - def __init__(self, config_path: Path | None = None): - """Initialize CrewAgents with optional custom config path.""" - self.config_path = config_path or get_crewbase_path() - self._crew: Crew | None = None - self._config: dict[str, Any] | None = None - - @property - def config(self) -> dict[str, Any]: - """Lazy load configuration.""" - if self._config is None: - with open(self.config_path) as f: - self._config = yaml.safe_load(f) - return self._config - - @property - def crew(self) -> Crew: - """Lazy load crew from YAML.""" - if self._crew is None: - self._crew = Crew.from_yaml(str(self.config_path)) # type: ignore[attr-defined] - return self._crew - - def _get_crew_for_task(self, task_name: str | None = None) -> Crew: - """Get a crew instance, optionally filtered to a specific task. - - When task filtering is needed, creates a fresh crew instance to avoid - mutating the cached crew's task list. - - Args: - task_name: Optional task name to filter to - - Returns: - Crew instance (cached if no filtering, fresh if filtering) - - Raises: - ValueError: If task_name is provided but not found - """ - if task_name is None: - return self.crew - - # Create fresh crew for task filtering to avoid mutating cached instance - crew = Crew.from_yaml(str(self.config_path)) # type: ignore[attr-defined] - matching_tasks = [t for t in crew.tasks if t.name == task_name] - - if not matching_tasks: - available = [t.name for t in crew.tasks] - raise ValueError(f"Task '{task_name}' not found. Available: {available}") - - crew.tasks = matching_tasks - return crew # type: ignore[return-value, no-any-return] - - def kickoff(self, inputs: dict[str, Any] | None = None) -> Any: - """Execute the crew with given inputs. - - Args: - inputs: Optional dict with: - - task: Specific task name to run (filters crew tasks) - - Any other inputs passed to crew.kickoff() - - Returns: - CrewOutput from crew execution - - Note: - This method does NOT mutate the inputs dict passed by caller. - """ - task_name = inputs.get("task") if inputs else None - # Create filtered inputs without mutating original - filtered_inputs = {k: v for k, v in inputs.items() if k != "task"} if inputs else None - crew = self._get_crew_for_task(task_name) - return crew.kickoff(inputs=filtered_inputs) - - def kickoff_async(self, inputs: dict[str, Any] | None = None) -> Any: - """Async version of kickoff for parallel execution. - - Note: - This method does NOT mutate the inputs dict passed by caller. - """ - task_name = inputs.get("task") if inputs else None - # Create filtered inputs without mutating original - filtered_inputs = {k: v for k, v in inputs.items() if k != "task"} if inputs else None - crew = self._get_crew_for_task(task_name) - return crew.kickoff_async(inputs=filtered_inputs) - - -def load_crewbase(): - """Load crewbase.yaml configuration.""" - crewbase_path = Path(__file__).parent.parent.parent / "crewbase.yaml" - with open(crewbase_path) as f: - return yaml.safe_load(f) - - -def kickoff(inputs: dict[str, Any] | None = None): - """Main entry point for crewai run. - - Args: - inputs: Optional dict with: - - task: Specific task name to run - - All other task inputs from crewbase.yaml - - Note: - This function does NOT mutate the inputs dict passed by caller. - """ - load_crewbase() - - # CrewAI will automatically load agents/tasks from crewbase.yaml - # The MCP tools are declared in crewbase.yaml using mcp:// syntax - crew = Crew.from_yaml(str(Path(__file__).parent.parent.parent / "crewbase.yaml")) # type: ignore[attr-defined] - - # If specific task requested, filter to that task - # Create filtered inputs without mutating original - task_name = inputs.get("task") if inputs else None - filtered_inputs = {k: v for k, v in inputs.items() if k != "task"} if inputs else None - - if task_name: - crew.tasks = [t for t in crew.tasks if t.name == task_name] - if not crew.tasks: - raise ValueError(f"Task '{task_name}' not found in crewbase.yaml") - - return crew.kickoff(inputs=filtered_inputs) - - -def train(n_iterations: int = 5, inputs: dict[str, Any] | None = None): - """Train the crew using memory/learning features.""" - load_crewbase() - crew = Crew.from_yaml(str(Path(__file__).parent.parent.parent / "crewbase.yaml")) # type: ignore[attr-defined] - - crew.train(n_iterations=n_iterations, inputs=inputs) - - -if __name__ == "__main__": - # For direct execution - kickoff() diff --git a/packages/agentic-fabric/src/agentic_fabric/main.py b/packages/agentic-fabric/src/agentic_fabric/main.py index ab702c4..440d738 100644 --- a/packages/agentic-fabric/src/agentic_fabric/main.py +++ b/packages/agentic-fabric/src/agentic_fabric/main.py @@ -9,17 +9,17 @@ agentic-fabric list --json # JSON output for external tools # List crews in a specific package - agentic-fabric list otterfall + agentic-fabric list my-package # Run a crew - agentic-fabric run otterfall game_builder --input "Create a BiomeComponent" - agentic-fabric run otterfall game_builder --input "..." --json # JSON output + agentic-fabric run my-package reviewer --input "Review this code" + agentic-fabric run my-package reviewer --input "..." --json # JSON output # Run with input from file - agentic-fabric run otterfall game_builder --file tasks.md + agentic-fabric run my-package reviewer --file tasks.md # Show crew details - agentic-fabric info otterfall game_builder --json + agentic-fabric info my-package reviewer --json """ from __future__ import annotations @@ -32,7 +32,6 @@ from pathlib import Path from agentic_fabric.core.discovery import discover_packages, get_crew_config, list_crews -from agentic_fabric.core.runner import run_crew def cmd_list(args): @@ -321,28 +320,6 @@ def _cmd_run_single_agent(args, use_json: bool, start_time: float): sys.exit(1) -def cmd_build(args): - """Legacy build command - runs otterfall game_builder.""" - print("=" * 60) - print("🎮 OTTERFALL GAME BUILDER") - print("=" * 60) - print() - print(f"Building: {args.spec[:100]}...") - - inputs = {"spec": args.spec, "component_spec": args.spec} - - try: - result = run_crew("otterfall", "game_builder", inputs) - print("\n" + "=" * 60) - print("📄 RESULT") - print("=" * 60) - print(result) - except ValueError as e: - print(f"❌ Error: {e}") - print("\nNote: The 'build' command requires packages/otterfall/.crewai/") - sys.exit(1) - - def cmd_info(args): """Show detailed info about a crew.""" use_json = getattr(args, "json", False) @@ -500,15 +477,15 @@ def main(): agentic-fabric list --json # JSON output for external tools # List crews in a package - agentic-fabric list otterfall + agentic-fabric list my-package # List available single-agent runners agentic-fabric list-runners agentic-fabric list-runners --json # Run a multi-agent crew - agentic-fabric run otterfall game_builder --input "Create a QuestComponent" - agentic-fabric run otterfall game_builder --input "..." --json # JSON output + agentic-fabric run my-package reviewer --input "Review this code" + agentic-fabric run my-package reviewer --input "..." --json # JSON output # Run with single-agent CLI runner agentic-fabric run --runner aider --input "Add error handling to auth.py" @@ -516,7 +493,7 @@ def main(): agentic-fabric run --runner ollama --input "Fix the bug" --model deepseek-coder # Show crew details - agentic-fabric info otterfall game_builder --json + agentic-fabric info my-package reviewer --json Exit codes: 0 - Success @@ -543,8 +520,8 @@ def main(): # Run command run_parser = subparsers.add_parser("run", help="Run a crew or single-agent task") - run_parser.add_argument("package", nargs="?", help="Package name (e.g., otterfall)") - run_parser.add_argument("crew", nargs="?", help="Crew name (e.g., game_builder)") + run_parser.add_argument("package", nargs="?", help="Package name (e.g., my-package)") + run_parser.add_argument("crew", nargs="?", help="Crew name (e.g., reviewer)") run_parser.add_argument("--input", "-i", help="Input specification") run_parser.add_argument("--file", "-f", help="Read input from file") run_parser.add_argument( @@ -577,14 +554,6 @@ def main(): info_parser.add_argument("crew", help="Crew name") info_parser.add_argument("--json", action="store_true", help="Output as JSON (for external tools)") - # Legacy build command (for backwards compatibility) - build_parser = subparsers.add_parser("build", help="Build a game component (legacy)") - build_parser.add_argument("spec", help="Component specification") - - # Legacy commands - subparsers.add_parser("list-knowledge", help="List knowledge sources (legacy)") - subparsers.add_parser("test-tools", help="Test file tools (legacy)") - args = parser.parse_args() if args.command == "list": @@ -595,24 +564,6 @@ def main(): cmd_run(args) elif args.command == "info": cmd_info(args) - elif args.command == "build": - cmd_build(args) - elif args.command == "list-knowledge": - # Legacy - list knowledge from otterfall - packages = discover_packages() - if "otterfall" in packages: - config = get_crew_config(packages["otterfall"], "game_builder") - print("Knowledge sources:") - for kp in config.get("knowledge_paths", []): - print(f" • {kp}") - else: - print("No otterfall package found.") - elif args.command == "test-tools": - from agentic_fabric.tools.file_tools import DirectoryListTool, get_workspace_root - - print(f"Workspace root: {get_workspace_root()}") - tool = DirectoryListTool() - print(tool._run("packages")) else: parser.print_help() diff --git a/packages/agentic-fabric/src/agentic_fabric/run_flow.py b/packages/agentic-fabric/src/agentic_fabric/run_flow.py deleted file mode 100644 index 1a5db50..0000000 --- a/packages/agentic-fabric/src/agentic_fabric/run_flow.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Utility to run CrewAI flows from command line.""" - -from __future__ import annotations - -import importlib -import sys - -from typing import Any, cast - - -def _load_flow(module_name: str, class_name: str) -> type[Any]: - module = importlib.import_module(module_name) - return cast(type[Any], getattr(module, class_name)) - - -def run_tdd_prototype(requirements: dict[str, Any]) -> Any: - """Run TDD Prototype Flow.""" - flow_cls = _load_flow("agentic_fabric.flows.tdd_prototype_flow", "TDDPrototypeFlow") - flow = flow_cls() - return flow.kickoff(inputs={"requirements": requirements}) - - -def run_meshy_asset(species: str, prompt: str, retexture_prompt: str) -> Any: - """Run Meshy Asset Flow.""" - flow_cls = _load_flow("agentic_fabric.flows.meshy_asset_flow", "MeshyAssetFlow") - flow = flow_cls() - return flow.kickoff(inputs={"species": species, "prompt": prompt, "retexture_prompt": retexture_prompt}) - - -def run_prototype_assessment(prototypes: list[Any]) -> Any: - """Run Prototype to Production Flow.""" - flow_cls = _load_flow( - "agentic_fabric.flows.prototype_to_production_flow", - "PrototypeToProductionFlow", - ) - flow = flow_cls() - return flow.kickoff(inputs={"prototypes": prototypes}) - - -def run_asset_integration(asset_manifest: dict[str, Any]) -> Any: - """Run Asset Integration Flow.""" - flow_cls = _load_flow("agentic_fabric.flows.asset_integration_flow", "AssetIntegrationFlow") - flow = flow_cls() - return flow.kickoff(inputs={"asset_manifest": asset_manifest}) - - -def run_hitl_review(content_type: str, content_url: str) -> Any: - """Run HITL Review Flow.""" - flow_cls = _load_flow("agentic_fabric.flows.hitl_review_flow", "HITLReviewFlow") - flow = flow_cls() - return flow.kickoff(inputs={"content_type": content_type, "content_url": content_url}) - - -def run_batch_generation(species_list: list[str]) -> Any: - """Run Batch Generation Flow.""" - flow_cls = _load_flow("agentic_fabric.flows.batch_generation_flow", "BatchGenerationFlow") - flow = flow_cls() - return flow.kickoff(inputs={"species_list": species_list}) - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python -m crew_agents.run_flow [args...]") - print("\nAvailable flows:") - print(" tdd_prototype") - print(" meshy_asset ") - print(" prototype_assessment ...") - print(" asset_integration") - print(" hitl_review ") - print(" batch_generation ...") - sys.exit(1) - - flow_name = sys.argv[1] - - # Validate flow name - valid_flows = [ - "tdd_prototype", - "meshy_asset", - "prototype_assessment", - "asset_integration", - "hitl_review", - "batch_generation", - ] - if flow_name not in valid_flows: - print(f"❌ Unknown flow: {flow_name}") - print(f"Valid flows: {', '.join(valid_flows)}") - sys.exit(1) - - try: - if flow_name == "tdd_prototype": - requirements = {"feature": "biome_selector"} - result = run_tdd_prototype(requirements) - print("\n✅ Flow completed successfully") - print(f"Result: {result}") - - elif flow_name == "meshy_asset": - species = sys.argv[2] if len(sys.argv) > 2 else "otter" - prompt = sys.argv[3] if len(sys.argv) > 3 else "A realistic otter" - retexture = sys.argv[4] if len(sys.argv) > 4 else "grey fur variant" - run_meshy_asset(species, prompt, retexture) - - elif flow_name == "prototype_assessment": - prototypes = sys.argv[2:] if len(sys.argv) > 2 else ["biome_selector_diorama"] - run_prototype_assessment(prototypes) - - elif flow_name == "asset_integration": - manifest = { - "species": sys.argv[2] if len(sys.argv) > 2 else "otter", - "glb_url": sys.argv[3] if len(sys.argv) > 3 else "", - "animations": [], - } - run_asset_integration(manifest) - - elif flow_name == "hitl_review": - content_type = sys.argv[2] if len(sys.argv) > 2 else "asset" - content_url = sys.argv[3] if len(sys.argv) > 3 else "" - run_hitl_review(content_type, content_url) - - elif flow_name == "batch_generation": - species_list = sys.argv[2:] if len(sys.argv) > 2 else ["otter", "beaver", "muskrat"] - run_batch_generation(species_list) - - except Exception as e: - print(f"\n❌ Flow execution failed: {e!s}") - import traceback - - traceback.print_exc() - sys.exit(1) diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py index 7833197..8ae02d9 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py @@ -24,7 +24,7 @@ import shlex import subprocess -from dataclasses import dataclass, field +from dataclasses import dataclass, field, fields from pathlib import Path from typing import Any @@ -76,6 +76,50 @@ class LocalCLIConfig: notes: str = "" +_CONFIG_FIELD_NAMES = {item.name for item in fields(LocalCLIConfig)} + + +def _validate_profile_config(name: str, config_dict: dict[str, Any]) -> dict[str, Any]: + """Validate a local CLI profile loaded from YAML.""" + unknown_fields = set(config_dict) - _CONFIG_FIELD_NAMES + if unknown_fields: + msg = f"Profile '{name}' has unsupported fields: {sorted(unknown_fields)}" + raise ValueError(msg) + + command = config_dict.get("command") + task_flag = config_dict.get("task_flag") + if not isinstance(command, str) or not command.strip(): + msg = f"Profile '{name}' must define a non-empty command string" + raise ValueError(msg) + if not isinstance(task_flag, str): + msg = f"Profile '{name}' must define task_flag as a string" + raise TypeError(msg) + + command_parts = shlex.split(command) + if not command_parts or any(part in {";", "&&", "||", "|"} for part in command_parts): + msg = f"Profile '{name}' command is not a direct executable invocation" + raise ValueError(msg) + + for field_name in ("auth_env", "additional_flags"): + value = config_dict.get(field_name, []) + if not isinstance(value, list) or not all(isinstance(item, str) and item for item in value): + msg = f"Profile '{name}' field '{field_name}' must be a list of non-empty strings" + raise ValueError(msg) + + for field_name in ("subcommand", "auto_approve", "structured_output", "model_flag", "default_model", "working_dir_flag"): + value = config_dict.get(field_name) + if value is not None and not isinstance(value, str): + msg = f"Profile '{name}' field '{field_name}' must be a string when provided" + raise ValueError(msg) + + timeout = config_dict.get("timeout", 300) + if not isinstance(timeout, int) or timeout <= 0 or timeout > 3600: + msg = f"Profile '{name}' timeout must be between 1 and 3600 seconds" + raise ValueError(msg) + + return config_dict + + class LocalCLIRunner(SingleAgentRunner): """Universal runner for CLI-based coding agents. @@ -124,7 +168,7 @@ def __init__( self.config = profiles[profile] elif isinstance(profile, dict): # Convert dict to LocalCLIConfig - self.config = LocalCLIConfig(**profile) + self.config = LocalCLIConfig(**_validate_profile_config("custom", profile)) else: # Use provided LocalCLIConfig directly self.config = profile @@ -155,7 +199,7 @@ def _load_profiles(cls) -> dict[str, LocalCLIConfig]: profiles = {} for name, config_dict in data.get("profiles", {}).items(): - profiles[name] = LocalCLIConfig(**config_dict) + profiles[name] = LocalCLIConfig(**_validate_profile_config(name, config_dict)) cls._profiles_cache = profiles return profiles diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py b/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py index de55664..2b231d1 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py @@ -79,6 +79,29 @@ def get_workspace_root(package_name: str | None = None) -> Path: ALLOWED_EXTENSIONS = {".ts", ".tsx", ".json", ".md"} +def _clean_relative_path(path_value: str) -> str: + """Normalize a user-provided relative path string.""" + clean_path = path_value.strip().replace("\\", "/") + if not clean_path or clean_path.startswith("/") or ".." in Path(clean_path).parts: + raise ValueError(f"Path traversal not allowed in '{clean_path}'") + return clean_path + + +def _resolve_workspace_path(path_value: str) -> tuple[str, Path]: + """Resolve a user path and ensure it remains inside the workspace root.""" + clean_path = _clean_relative_path(path_value) + workspace_root = get_workspace_root().resolve() + full_path = (workspace_root / clean_path).resolve(strict=False) + if not full_path.is_relative_to(workspace_root): + raise ValueError(f"Path '{clean_path}' escapes workspace root") + return clean_path, full_path + + +def _is_allowed_write_path(clean_path: str) -> bool: + """Return whether a normalized path is under an allowed write directory.""" + return any(clean_path == allowed_dir or clean_path.startswith(f"{allowed_dir}/") for allowed_dir in ALLOWED_WRITE_DIRS) + + class WriteFileInput(BaseModel): """Input schema for GameCodeWriterTool.""" @@ -121,16 +144,10 @@ class GameCodeWriterTool(BaseTool): def _run(self, file_path: str, content: str) -> str: """Write the file content to the specified path.""" try: - # Validate path - clean_path = file_path.strip().replace("\\", "/") - - # Check for path traversal - if ".." in clean_path or clean_path.startswith("/"): - return f"Error: Invalid path '{clean_path}'. Path traversal not allowed." + clean_path, full_path = _resolve_workspace_path(file_path) # Check allowed directories - is_allowed = any(clean_path.startswith(allowed_dir) for allowed_dir in ALLOWED_WRITE_DIRS) - if not is_allowed: + if not _is_allowed_write_path(clean_path): return f"Error: Path '{clean_path}' is not in an allowed directory. Allowed: {ALLOWED_WRITE_DIRS}" # Check extension @@ -138,10 +155,6 @@ def _run(self, file_path: str, content: str) -> str: if ext not in ALLOWED_EXTENSIONS: return f"Error: Extension '{ext}' not allowed. Allowed: {ALLOWED_EXTENSIONS}" - # Construct full path - workspace_root = get_workspace_root() - full_path = workspace_root / clean_path - # Create parent directories full_path.parent.mkdir(parents=True, exist_ok=True) @@ -153,6 +166,8 @@ def _run(self, file_path: str, content: str) -> str: except PermissionError: return f"Error: Permission denied writing to {file_path}" + except ValueError as e: + return f"Error: {e!s}" except Exception as e: return f"Error writing file: {e!s}" @@ -188,13 +203,7 @@ class GameCodeReaderTool(BaseTool): def _run(self, file_path: str) -> str: """Read the file content from the specified path.""" try: - clean_path = file_path.strip().replace("\\", "/") - - if ".." in clean_path: - return f"Error: Path traversal not allowed in '{clean_path}'" - - workspace_root = get_workspace_root() - full_path = workspace_root / clean_path + clean_path, full_path = _resolve_workspace_path(file_path) if not full_path.exists(): return f"Error: File not found: {clean_path}" @@ -211,6 +220,8 @@ def _run(self, file_path: str) -> str: except PermissionError: return f"Error: Permission denied reading {file_path}" + except ValueError as e: + return f"Error: {e!s}" except Exception as e: return f"Error reading file: {e!s}" @@ -246,13 +257,7 @@ class DirectoryListTool(BaseTool): def _run(self, directory: str) -> str: """List directory contents.""" try: - clean_path = directory.strip().replace("\\", "/") - - if ".." in clean_path: - return "Error: Path traversal not allowed" - - workspace_root = get_workspace_root() - full_path = workspace_root / clean_path + clean_path, full_path = _resolve_workspace_path(directory) if not full_path.exists(): return f"Error: Directory not found: {clean_path}" @@ -274,5 +279,7 @@ def _run(self, directory: str) -> str: except PermissionError: return f"Error: Permission denied accessing {directory}" + except ValueError as e: + return f"Error: {e!s}" except Exception as e: return f"Error listing directory: {e!s}" diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/registry.py b/packages/agentic-fabric/src/agentic_fabric/tools/registry.py index c6ed1f5..c71c429 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/registry.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/registry.py @@ -8,6 +8,7 @@ import importlib import logging +import os from collections.abc import Callable from typing import Any @@ -49,12 +50,33 @@ def factory() -> Any: "mcp://filesystem/list_directory": "DirectoryListTool", } +_DEFAULT_IMPORT_PREFIXES = ("agentic_fabric.",) + def _canonical_tool_name(tool_name: str) -> str: """Normalize aliases to a canonical tool identifier.""" return _TOOL_ALIASES.get(tool_name, tool_name) +def register_tool_factory(tool_name: str, factory: ToolFactory, *, aliases: tuple[str, ...] = ()) -> None: + """Register a safe tool factory without using dynamic imports.""" + _TOOL_FACTORIES[tool_name] = factory + for alias in aliases: + _TOOL_ALIASES[alias] = tool_name + + +def _allowed_import_prefixes() -> tuple[str, ...]: + """Return module prefixes allowed for fully qualified tool references.""" + configured = os.environ.get("AGENTIC_FABRIC_TOOL_IMPORT_ALLOWLIST", "") + extra = tuple(prefix.strip() for prefix in configured.split(",") if prefix.strip()) + return (*_DEFAULT_IMPORT_PREFIXES, *extra) + + +def _is_import_allowed(module_name: str) -> bool: + """Return whether a fully qualified tool module may be imported.""" + return any(module_name == prefix.rstrip(".") or module_name.startswith(prefix) for prefix in _allowed_import_prefixes()) + + def _resolve_vendor_tool(tool_name: str) -> Any | None: """Resolve vendor-backed tool references without importing vendor SDKs.""" if tool_name.startswith("vendor://"): @@ -99,11 +121,17 @@ def resolve_tool(tool_name: str) -> Any | None: try: if ":" in canonical_name: module_name, attr_name = canonical_name.split(":", 1) + if not _is_import_allowed(module_name): + logger.warning("Skipping tool '%s': module is not in AGENTIC_FABRIC_TOOL_IMPORT_ALLOWLIST", tool_name) + return None return _build_factory(module_name, attr_name)() if "." in canonical_name: module_name, _, attr_name = canonical_name.rpartition(".") if module_name and attr_name: + if not _is_import_allowed(module_name): + logger.warning("Skipping tool '%s': module is not in AGENTIC_FABRIC_TOOL_IMPORT_ALLOWLIST", tool_name) + return None return _build_factory(module_name, attr_name)() except (ImportError, AttributeError) as exc: logger.warning("Failed to resolve tool '%s': %s", tool_name, exc) diff --git a/packages/agentic-fabric/tests/test_file_tool_paths.py b/packages/agentic-fabric/tests/test_file_tool_paths.py new file mode 100644 index 0000000..8c81dec --- /dev/null +++ b/packages/agentic-fabric/tests/test_file_tool_paths.py @@ -0,0 +1,50 @@ +"""Security tests for file tool path resolution helpers.""" + +from __future__ import annotations + +import importlib +import sys +import types + +from pathlib import Path + +import pytest + + +def import_file_tools_with_fake_crewai(monkeypatch: pytest.MonkeyPatch) -> types.ModuleType: + """Import file_tools without requiring the optional CrewAI package.""" + fake_crewai = types.ModuleType("crewai") + fake_tools = types.ModuleType("crewai.tools") + + class BaseTool: + """Minimal stand-in for crewai.tools.BaseTool.""" + + fake_tools.BaseTool = BaseTool + monkeypatch.setitem(sys.modules, "crewai", fake_crewai) + monkeypatch.setitem(sys.modules, "crewai.tools", fake_tools) + sys.modules.pop("agentic_fabric.tools.file_tools", None) + return importlib.import_module("agentic_fabric.tools.file_tools") + + +def test_resolve_workspace_path_rejects_symlink_escape(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Resolved paths must remain below the workspace root after symlinks.""" + file_tools = import_file_tools_with_fake_crewai(monkeypatch) + workspace_root = tmp_path / "workspace" + workspace_root.mkdir() + outside = tmp_path / "outside" + outside.mkdir() + (workspace_root / "src").mkdir() + (workspace_root / "src" / "ecs").symlink_to(outside, target_is_directory=True) + + monkeypatch.setattr(file_tools, "get_workspace_root", lambda: workspace_root) + + with pytest.raises(ValueError, match="escapes workspace root"): + file_tools._resolve_workspace_path("src/ecs/secret.ts") + + +def test_allowed_write_path_requires_directory_boundary(monkeypatch: pytest.MonkeyPatch) -> None: + """Allowed write dirs match real path segments, not arbitrary prefixes.""" + file_tools = import_file_tools_with_fake_crewai(monkeypatch) + + assert file_tools._is_allowed_write_path("src/ecs/component.ts") + assert not file_tools._is_allowed_write_path("src/ecsnot/component.ts") diff --git a/packages/agentic-fabric/tests/test_single_agent_runners.py b/packages/agentic-fabric/tests/test_single_agent_runners.py index 145a78a..f030e08 100644 --- a/packages/agentic-fabric/tests/test_single_agent_runners.py +++ b/packages/agentic-fabric/tests/test_single_agent_runners.py @@ -173,6 +173,26 @@ def test_init_with_config_dict(self): assert runner.config.task_flag == "--prompt" assert runner.config.auto_approve == "--yes" + def test_rejects_unknown_config_dict_fields(self): + """Custom config dicts should reject unsupported fields.""" + with pytest.raises(ValueError, match="unsupported fields"): + LocalCLIRunner({"command": "my-tool", "task_flag": "--prompt", "shell": True}) + + def test_rejects_shell_operator_in_command(self): + """Profile commands should be direct executable invocations.""" + with pytest.raises(ValueError, match="direct executable"): + LocalCLIRunner({"command": "my-tool && rm -rf /", "task_flag": "--prompt"}) + + def test_rejects_invalid_additional_flags(self): + """List fields should contain only non-empty strings.""" + with pytest.raises(ValueError, match="additional_flags"): + LocalCLIRunner({"command": "my-tool", "task_flag": "--prompt", "additional_flags": ["--ok", ""]}) + + def test_rejects_invalid_timeout(self): + """Timeouts should stay within the bounded execution range.""" + with pytest.raises(ValueError, match="timeout"): + LocalCLIRunner({"command": "my-tool", "task_flag": "--prompt", "timeout": 0}) + def test_init_with_config_object(self): """Should initialize with a LocalCLIConfig object.""" config = LocalCLIConfig( diff --git a/packages/agentic-fabric/tests/test_tool_registry.py b/packages/agentic-fabric/tests/test_tool_registry.py index e0f3aaa..1e8296e 100644 --- a/packages/agentic-fabric/tests/test_tool_registry.py +++ b/packages/agentic-fabric/tests/test_tool_registry.py @@ -2,9 +2,11 @@ from __future__ import annotations +import os + from unittest.mock import MagicMock, patch -from agentic_fabric.tools.registry import resolve_tool, resolve_tools +from agentic_fabric.tools.registry import register_tool_factory, resolve_tool, resolve_tools from agentic_fabric.tools.vendor import VendorCapabilityTool @@ -30,7 +32,7 @@ def test_resolves_builtin_alias(self, mock_import_module: MagicMock) -> None: @patch("agentic_fabric.tools.registry.importlib.import_module") def test_resolves_fully_qualified_reference(self, mock_import_module: MagicMock) -> None: - """module:attribute references should be resolved dynamically.""" + """Allowed module:attribute references should be resolved dynamically.""" mock_tool_class = MagicMock() mock_tool_instance = MagicMock() mock_tool_class.return_value = mock_tool_instance @@ -39,12 +41,31 @@ def test_resolves_fully_qualified_reference(self, mock_import_module: MagicMock) mock_module.CustomTool = mock_tool_class mock_import_module.return_value = mock_module - result = resolve_tool("custom.module:CustomTool") + with patch.dict(os.environ, {"AGENTIC_FABRIC_TOOL_IMPORT_ALLOWLIST": "custom.module"}): + result = resolve_tool("custom.module:CustomTool") mock_import_module.assert_called_once_with("custom.module") mock_tool_class.assert_called_once_with() assert result is mock_tool_instance + @patch("agentic_fabric.tools.registry.importlib.import_module") + def test_skips_unallowed_fully_qualified_reference(self, mock_import_module: MagicMock) -> None: + """Fully qualified references should require an explicit allowlist.""" + result = resolve_tool("custom.module:CustomTool") + + assert result is None + mock_import_module.assert_not_called() + + def test_resolves_registered_factory_alias(self) -> None: + """Application code can register safe tool factories directly.""" + tool = object() + factory = MagicMock(return_value=tool) + + register_tool_factory("UnitTestTool", factory, aliases=("unit-test-tool",)) + + assert resolve_tool("unit-test-tool") is tool + factory.assert_called_once_with() + def test_returns_none_for_unresolved_mcp_tool(self) -> None: """Unsupported MCP tool references should be skipped cleanly.""" assert resolve_tool("mcp://git/execute_command") is None diff --git a/packages/agentic-fabric/tests/test_yaml_config_errors.py b/packages/agentic-fabric/tests/test_yaml_config_errors.py index 40339bd..e4c9c45 100644 --- a/packages/agentic-fabric/tests/test_yaml_config_errors.py +++ b/packages/agentic-fabric/tests/test_yaml_config_errors.py @@ -148,6 +148,34 @@ def test_nonexistent_knowledge_paths_excluded(self, tmp_path: Path) -> None: config = get_crew_config(tmp_path, "test_crew") assert config["knowledge_paths"] == [] + def test_agents_path_cannot_escape_config_dir(self, tmp_path: Path) -> None: + """Manifest agents paths must stay inside the config directory.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " test_crew:\n" + " agents: ../outside_agents.yaml\n" + " tasks: tasks.yaml\n" + ) + + with pytest.raises(ValueError, match="Manifest path must be relative"): + get_crew_config(tmp_path, "test_crew") + + def test_knowledge_path_cannot_escape_config_dir(self, tmp_path: Path) -> None: + """Manifest knowledge paths must stay inside the config directory.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " test_crew:\n" + " agents: agents.yaml\n" + " tasks: tasks.yaml\n" + " knowledge:\n" + " - ../knowledge\n" + ) + + with pytest.raises(ValueError, match="Manifest path must be relative"): + get_crew_config(tmp_path, "test_crew") + class TestGetFrameworkFromConfigDir: """Test edge cases for get_framework_from_config_dir.""" From 2b0e5bc09c600e651be7fc04953bfadb80c318ec Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 00:23:40 -0500 Subject: [PATCH 04/35] fix: address review robustness findings --- .../src/agentic_fabric/core/discovery.py | 8 +- .../src/agentic_fabric/core/loader.py | 2 +- .../agentic_fabric/runners/crewai_runner.py | 2 +- .../runners/local_cli_runner.py | 15 +++- .../agentic_fabric/tools/scraping_tools.py | 7 +- .../src/agentic_fabric/utils/files.py | 2 +- .../tests/test_discovery_nested.py | 16 ++++ .../tests/test_scraping_tool_crawler.py | 74 +++++++++++++++++++ .../tests/test_single_agent_runners.py | 14 ++++ .../agentic-fabric/tests/test_utils_files.py | 15 ++++ .../tests/test_yaml_config_errors.py | 17 +++++ 11 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 packages/agentic-fabric/tests/test_scraping_tool_crawler.py create mode 100644 packages/agentic-fabric/tests/test_utils_files.py diff --git a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py index 8120dcf..4940be0 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py @@ -85,7 +85,7 @@ def discover_packages( # Check packages/ directory packages_dir = workspace_root / "packages" - if packages_dir.exists(): + if packages_dir.is_dir(): for pkg_dir in packages_dir.iterdir(): if not pkg_dir.is_dir(): continue @@ -132,7 +132,7 @@ def discover_all_framework_configs( # Check packages/ directory packages_dir = workspace_root / "packages" - if packages_dir.exists(): + if packages_dir.is_dir(): for pkg_dir in packages_dir.iterdir(): if not pkg_dir.is_dir(): continue @@ -173,7 +173,7 @@ def load_manifest(crewai_dir: Path) -> dict[str, Any]: Parsed manifest as a dictionary. """ manifest_path = crewai_dir / "manifest.yaml" - with open(manifest_path) as f: + with open(manifest_path, encoding="utf-8") as f: result = yaml.safe_load(f) return result or {} @@ -240,7 +240,7 @@ def get_crew_config(config_dir: Path, crew_name: str) -> dict: knowledge_paths = [] for kp in crew_config.get("knowledge", []): full_path = _resolve_config_path(config_dir, kp) - if full_path.exists(): + if full_path.is_dir(): knowledge_paths.append(full_path) # Determine required framework from directory name diff --git a/packages/agentic-fabric/src/agentic_fabric/core/loader.py b/packages/agentic-fabric/src/agentic_fabric/core/loader.py index 02942c9..389bee5 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/loader.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/loader.py @@ -39,7 +39,7 @@ def load_knowledge_sources(knowledge_paths: list[Path]) -> list: sources = [] for knowledge_path in knowledge_paths: - if not knowledge_path.exists(): + if not knowledge_path.is_dir(): continue # Load .md and .ts files diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py index e22f682..86c68f2 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py @@ -180,7 +180,7 @@ def _load_knowledge(self, knowledge_paths: list[Path]) -> list: sources = [] for knowledge_path_value in knowledge_paths: knowledge_path = knowledge_path_value if isinstance(knowledge_path_value, Path) else Path(knowledge_path_value) - if not knowledge_path.exists(): + if not knowledge_path.is_dir(): continue for ext in ["*.md", "*.txt", "*.py", "*.ts"]: diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py index 8ae02d9..c5a4d2e 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py @@ -22,6 +22,7 @@ import os import shlex +import stat import subprocess from dataclasses import dataclass, field, fields @@ -120,6 +121,17 @@ def _validate_profile_config(name: str, config_dict: dict[str, Any]) -> dict[str return config_dict +def _validate_profiles_file_permissions(profiles_file: Path) -> None: + """Reject local CLI profile files writable by group or other users.""" + if os.name != "posix": + return + + mode = profiles_file.stat().st_mode + if mode & (stat.S_IWGRP | stat.S_IWOTH): + msg = f"Profiles file is writable by group or other users: {profiles_file}" + raise PermissionError(msg) + + class LocalCLIRunner(SingleAgentRunner): """Universal runner for CLI-based coding agents. @@ -192,9 +204,10 @@ def _load_profiles(cls) -> dict[str, LocalCLIConfig]: raise FileNotFoundError( f"Profiles file not found: {profiles_file}\nExpected local_cli_profiles.yaml in runners directory." ) + _validate_profiles_file_permissions(profiles_file) # Load and parse YAML - with open(profiles_file) as f: + with open(profiles_file, encoding="utf-8") as f: data = yaml.safe_load(f) profiles = {} diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py b/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py index ae179a6..a576328 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py @@ -4,6 +4,7 @@ import logging +from collections import deque from urllib.parse import urljoin, urlparse import requests @@ -36,18 +37,18 @@ def _run(self, url: str) -> str: """ scraped_content = "" visited_urls = set() - urls_to_visit = [url] + urls_to_visit = deque([url]) base_netloc = urlparse(url).netloc while urls_to_visit: - current_url = urls_to_visit.pop(0) + current_url = urls_to_visit.popleft() if current_url in visited_urls: continue + visited_urls.add(current_url) try: response = requests.get(current_url, timeout=30) response.raise_for_status() - visited_urls.add(current_url) soup = BeautifulSoup(response.content, "html.parser") scraped_content += self._scrape_content(soup) diff --git a/packages/agentic-fabric/src/agentic_fabric/utils/files.py b/packages/agentic-fabric/src/agentic_fabric/utils/files.py index dd267de..08a28e5 100644 --- a/packages/agentic-fabric/src/agentic_fabric/utils/files.py +++ b/packages/agentic-fabric/src/agentic_fabric/utils/files.py @@ -17,6 +17,6 @@ def load_config(path: Path | str) -> dict[str, Any]: Returns: A dictionary containing the configuration. """ - with open(path) as f: + with open(path, encoding="utf-8") as f: loaded = yaml.safe_load(f) or {} return cast(dict[str, Any], loaded) diff --git a/packages/agentic-fabric/tests/test_discovery_nested.py b/packages/agentic-fabric/tests/test_discovery_nested.py index 0a014df..51b3575 100644 --- a/packages/agentic-fabric/tests/test_discovery_nested.py +++ b/packages/agentic-fabric/tests/test_discovery_nested.py @@ -65,6 +65,14 @@ def test_packages_dir_not_present(self, tmp_path: Path) -> None: packages = discover_packages(workspace_root=tmp_path) assert packages == {} + def test_packages_path_as_file_is_ignored(self, tmp_path: Path) -> None: + """A file named packages should not crash package discovery.""" + (tmp_path / "packages").write_text("not a directory") + + packages = discover_packages(workspace_root=tmp_path) + + assert packages == {} + def test_framework_filter_crewai(self, tmp_path: Path) -> None: """Filtering by framework='crewai' should only find .crewai dirs.""" pkg_dir = tmp_path / "packages" / "test_pkg" @@ -148,6 +156,14 @@ def test_empty_workspace(self, tmp_path: Path) -> None: configs = discover_all_framework_configs(workspace_root=tmp_path) assert configs == {} + def test_packages_path_as_file_is_ignored(self, tmp_path: Path) -> None: + """A file named packages should not crash all-framework discovery.""" + (tmp_path / "packages").write_text("not a directory") + + configs = discover_all_framework_configs(workspace_root=tmp_path) + + assert configs == {} + class TestFrameworkConstants: """Test the framework constant mappings.""" diff --git a/packages/agentic-fabric/tests/test_scraping_tool_crawler.py b/packages/agentic-fabric/tests/test_scraping_tool_crawler.py new file mode 100644 index 0000000..1037696 --- /dev/null +++ b/packages/agentic-fabric/tests/test_scraping_tool_crawler.py @@ -0,0 +1,74 @@ +"""Crawler behavior tests without optional scraping dependencies.""" + +from __future__ import annotations + +import importlib +import sys +import types + +from unittest.mock import MagicMock + +import pytest + + +def import_scraping_tools(monkeypatch: pytest.MonkeyPatch) -> types.ModuleType: + """Import scraping_tools with lightweight optional dependency stand-ins.""" + fake_crewai_tools = types.ModuleType("crewai_tools") + fake_requests = types.ModuleType("requests") + fake_bs4 = types.ModuleType("bs4") + + class ScrapeWebsiteTool: + """Minimal stand-in for crewai_tools.ScrapeWebsiteTool.""" + + class RequestError(Exception): + """Minimal stand-in for requests.RequestException.""" + + class FakeLink(dict): + """BeautifulSoup-like link node for href access.""" + + class BeautifulSoup: + """Minimal parser for the crawler test fixture.""" + + def __init__(self, content: bytes, parser: str): + self.content = content.decode("utf-8") + + def __call__(self, tags: list[str]) -> list[object]: + return [] + + def find_all(self, tag: str, href: bool = False) -> list[FakeLink]: + if tag == "a" and href and "duplicate-fail" in self.content: + return [FakeLink(href="/fail"), FakeLink(href="/fail")] + return [] + + @property + def stripped_strings(self) -> list[str]: + return ["duplicate-fail"] if "duplicate-fail" in self.content else [] + + fake_crewai_tools.ScrapeWebsiteTool = ScrapeWebsiteTool + fake_requests.RequestException = RequestError + fake_requests.get = MagicMock() + fake_bs4.BeautifulSoup = BeautifulSoup + + monkeypatch.setitem(sys.modules, "crewai_tools", fake_crewai_tools) + monkeypatch.setitem(sys.modules, "requests", fake_requests) + monkeypatch.setitem(sys.modules, "bs4", fake_bs4) + sys.modules.pop("agentic_fabric.tools.scraping_tools", None) + return importlib.import_module("agentic_fabric.tools.scraping_tools") + + +def test_crawler_marks_failed_url_visited_before_retry(monkeypatch: pytest.MonkeyPatch) -> None: + """Duplicate failed URLs should not be requested repeatedly.""" + scraping_tools = import_scraping_tools(monkeypatch) + root_response = MagicMock() + root_response.content = b"duplicate-failduplicate-fail" + root_response.raise_for_status.return_value = None + + scraping_tools.requests.get.side_effect = [ + root_response, + scraping_tools.requests.RequestException("boom"), + ] + + result = scraping_tools.CrawlWebsiteTool()._run("https://example.test") + + assert "duplicate-fail" in result + assert scraping_tools.requests.get.call_count == 2 diff --git a/packages/agentic-fabric/tests/test_single_agent_runners.py b/packages/agentic-fabric/tests/test_single_agent_runners.py index f030e08..3f30ca2 100644 --- a/packages/agentic-fabric/tests/test_single_agent_runners.py +++ b/packages/agentic-fabric/tests/test_single_agent_runners.py @@ -2,6 +2,7 @@ from __future__ import annotations +import os import subprocess from pathlib import Path @@ -135,6 +136,19 @@ def test_load_profiles(self, mock_profiles_file: Path): assert profiles["test-tool"].command == "test-tool" assert profiles["aider"].auth_env == ["OPENAI_API_KEY"] + @pytest.mark.skipif(os.name != "posix", reason="POSIX file mode check") + def test_rejects_group_writable_profiles_file(self, mock_profiles_file: Path): + """Bundled local CLI profiles should not be group or world writable.""" + mock_profiles_file.chmod(0o664) + with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: + mock_path.return_value.parent = mock_profiles_file.parent + mock_path.return_value.__truediv__.return_value = mock_profiles_file + + LocalCLIRunner._profiles_cache = None + + with pytest.raises(PermissionError, match="writable by group or other"): + LocalCLIRunner._load_profiles() + def test_init_with_profile_name(self, mock_profiles_file: Path): """Should initialize with a profile name.""" with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: diff --git a/packages/agentic-fabric/tests/test_utils_files.py b/packages/agentic-fabric/tests/test_utils_files.py new file mode 100644 index 0000000..bfbc041 --- /dev/null +++ b/packages/agentic-fabric/tests/test_utils_files.py @@ -0,0 +1,15 @@ +"""Tests for utility file helpers.""" + +from __future__ import annotations + +from pathlib import Path + +from agentic_fabric.utils.files import load_config + + +def test_load_config_reads_utf8_yaml(tmp_path: Path) -> None: + """YAML files should be decoded as UTF-8 across platforms.""" + config_file = tmp_path / "config.yaml" + config_file.write_text("description: café\n", encoding="utf-8") + + assert load_config(config_file) == {"description": "café"} diff --git a/packages/agentic-fabric/tests/test_yaml_config_errors.py b/packages/agentic-fabric/tests/test_yaml_config_errors.py index e4c9c45..caf7ab1 100644 --- a/packages/agentic-fabric/tests/test_yaml_config_errors.py +++ b/packages/agentic-fabric/tests/test_yaml_config_errors.py @@ -148,6 +148,23 @@ def test_nonexistent_knowledge_paths_excluded(self, tmp_path: Path) -> None: config = get_crew_config(tmp_path, "test_crew") assert config["knowledge_paths"] == [] + def test_knowledge_file_paths_excluded(self, tmp_path: Path) -> None: + """Knowledge paths must be directories, not plain files.""" + (tmp_path / "knowledge.md").write_text("# Not a directory") + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " test_crew:\n" + " agents: agents.yaml\n" + " tasks: tasks.yaml\n" + " knowledge:\n" + " - knowledge.md\n" + ) + + config = get_crew_config(tmp_path, "test_crew") + + assert config["knowledge_paths"] == [] + def test_agents_path_cannot_escape_config_dir(self, tmp_path: Path) -> None: """Manifest agents paths must stay inside the config directory.""" manifest_file = tmp_path / "manifest.yaml" From 67a2c3c2e1f2c553c41671132c3737bc24dd9318 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 00:39:41 -0500 Subject: [PATCH 05/35] test: expand runtime coverage --- .../agentic-fabric/tests/test_agentic_data.py | 120 +++++++ .../agentic-fabric/tests/test_capabilities.py | 45 ++- .../agentic-fabric/tests/test_cli_commands.py | 337 ++++++++++++++++++ .../tests/test_connector_builder_crew.py | 117 ++++-- .../agentic-fabric/tests/test_core_runner.py | 92 +++++ .../tests/test_file_tool_paths.py | 118 ++++++ .../agentic-fabric/tests/test_llm_config.py | 133 +++++++ .../agentic-fabric/tests/test_loader_unit.py | 210 +++++++++++ .../tests/test_runtime_registry.py | 115 ++++++ .../tests/test_tool_adapters.py | 68 +++- .../agentic-fabric/tests/test_tools_init.py | 89 +++++ 11 files changed, 1415 insertions(+), 29 deletions(-) create mode 100644 packages/agentic-fabric/tests/test_cli_commands.py create mode 100644 packages/agentic-fabric/tests/test_core_runner.py create mode 100644 packages/agentic-fabric/tests/test_llm_config.py create mode 100644 packages/agentic-fabric/tests/test_loader_unit.py create mode 100644 packages/agentic-fabric/tests/test_runtime_registry.py create mode 100644 packages/agentic-fabric/tests/test_tools_init.py diff --git a/packages/agentic-fabric/tests/test_agentic_data.py b/packages/agentic-fabric/tests/test_agentic_data.py index fda0e39..67eb658 100644 --- a/packages/agentic-fabric/tests/test_agentic_data.py +++ b/packages/agentic-fabric/tests/test_agentic_data.py @@ -83,3 +83,123 @@ def test_fallback_vendor_guidance_when_vendor_fabric_missing() -> None: with pytest.raises(ImportError, match="vendor-fabric"): data.open("github") + + +def test_fallback_vendor_records_provider_when_not_strict() -> None: + """The no-vendor fallback should still keep non-strict provider context.""" + data = AgenticData() + if data.vendor_fabric_available: + pytest.skip("vendor-fabric is installed in this environment") + + assert data.open("github", strict=False) is data + assert data.active_provider == "github" + + with pytest.raises(ImportError, match="Vendor operation 'sync' requires vendor-fabric"): + data.call("sync") + + +def test_agent_registry_is_read_only_and_unregister_is_chainable() -> None: + """Agent registry snapshots should be immutable from the public property.""" + data = AgenticData(agent_registry={"reviewer": {"name": "review"}}) + + with pytest.raises(TypeError): + data.agent_registry["writer"] = {"name": "write"} # type: ignore[index] + + assert data.unregister_agent("reviewer") is data + assert data.agent_registry == {} + assert data.unregister_agent("missing") is data + + +def test_use_runtime_auto_and_clear_runtime(monkeypatch: pytest.MonkeyPatch) -> None: + """The auto runtime token should select a concrete runtime and clear cleanly.""" + monkeypatch.setattr(AgenticData, "select_runtime", lambda self: "crewai") + + data = AgenticData().use_runtime(" auto ", strict=False) + + assert data.active_runtime == "crewai" + assert data.clear_runtime() is data + assert data.active_runtime is None + + +def test_runtime_metadata_methods_delegate_to_registry(monkeypatch: pytest.MonkeyPatch) -> None: + """Runtime metadata helpers should normalize registry responses.""" + monkeypatch.setattr( + "agentic_fabric.agentic_data.runtime_info", + lambda runtime=None: {"name": runtime or "crewai", "available": True}, + ) + + data = AgenticData() + + assert data.runtimes() == [{"name": "crewai", "available": True}] + assert data.runtime_info("langgraph") == {"name": "langgraph", "available": True} + + +def test_select_runtime_rejects_explicit_manifest_conflict(monkeypatch: pytest.MonkeyPatch) -> None: + """Explicit runtime and manifest runtime requirements should agree.""" + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: True) + data = AgenticData() + + with pytest.raises(ValueError, match="Crew requires crewai but langgraph was requested"): + data.select_runtime("langgraph", crew_config={"runtime": "crewai"}) + + +def test_select_runtime_uses_required_runtime_and_auto_detection(monkeypatch: pytest.MonkeyPatch) -> None: + """Manifest requirements should win before auto-detection fallback.""" + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: True) + monkeypatch.setattr("agentic_fabric.core.decomposer.detect_framework", lambda: "strands") + data = AgenticData() + + assert data.select_runtime(crew_config={"framework": "langgraph"}) == "langgraph" + assert data.select_runtime() == "strands" + + +def test_select_runtime_reports_unavailable_requested_runtime(monkeypatch: pytest.MonkeyPatch) -> None: + """Unavailable explicit runtimes should raise install guidance.""" + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: False) + + with pytest.raises(RuntimeUnavailableError, match="agentic-fabric\\[crewai\\]"): + AgenticData().select_runtime("crewai") + + +def test_run_agent_accepts_direct_mapping(monkeypatch: pytest.MonkeyPatch) -> None: + """Direct crew config mappings should bypass registry lookup.""" + calls: list[tuple[dict[str, Any], dict[str, Any], str | None]] = [] + + def fake_run_crew_auto(crew_config: dict[str, Any], inputs: dict[str, Any], framework: str | None = None) -> str: + calls.append((crew_config, inputs, framework)) + return "done" + + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: runtime == "crewai") + monkeypatch.setattr("agentic_fabric.core.decomposer.run_crew_auto", fake_run_crew_auto) + + result = AgenticData().run_agent({"name": "direct", "runtime": "crewai"}, {"a": 1}, b=2) + + assert result == "done" + assert calls == [({"name": "direct", "runtime": "crewai"}, {"a": 1, "b": 2}, "crewai")] + + +def test_call_runtime_delegates_to_selected_runner(monkeypatch: pytest.MonkeyPatch) -> None: + """Runtime capability calls should use the selected runner.""" + + class FakeRunner: + def call_capability(self, capability: str, *args: Any, **kwargs: Any) -> tuple[Any, ...]: + return (capability, args, kwargs) + + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: True) + monkeypatch.setattr("agentic_fabric.core.decomposer.get_runner", lambda runtime: FakeRunner()) + + assert AgenticData().call_runtime("run", 1, runtime="crewai", flag=True) == ("run", (1,), {"flag": True}) + + +def test_dynamic_helpers_and_missing_attributes(monkeypatch: pytest.MonkeyPatch) -> None: + """Registered agents should appear as dynamic run_ helpers.""" + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: True) + monkeypatch.setattr("agentic_fabric.core.decomposer.run_crew_auto", lambda crew_config, inputs, framework: "done") + + data = AgenticData(agent_registry={"reviewer": {"name": "reviewer", "runtime": "crewai"}}) + + assert "run_reviewer" in dir(data) + assert data.run_reviewer() == "done" + + with pytest.raises(AttributeError, match="has no attribute 'missing'"): + data.missing diff --git a/packages/agentic-fabric/tests/test_capabilities.py b/packages/agentic-fabric/tests/test_capabilities.py index f38ec24..90e05b5 100644 --- a/packages/agentic-fabric/tests/test_capabilities.py +++ b/packages/agentic-fabric/tests/test_capabilities.py @@ -6,7 +6,12 @@ import pytest -from agentic_fabric.capabilities import agent_capability +from agentic_fabric.capabilities import ( + AgentCapabilityProviderMixin, + AgentCapabilitySpec, + agent_capability, + tool_capability, +) from agentic_fabric.runners.base import BaseRunner @@ -55,3 +60,41 @@ class TestProvider(AgentCapabilityProviderMixin, CustomProvider): assert provider.agent_capabilities["summary"].name == "summarize" assert provider.call_capability("summary", "hello") == "HELLO" + + def test_capability_metadata_and_kind_filtering(self) -> None: + """Capability metadata should serialize and filter by kind.""" + + class ToolProvider(AgentCapabilityProviderMixin): + @tool_capability("read_file", aliases=("read",), description="Read a file.") + def read_file(self) -> str: + return "content" + + provider = ToolProvider() + specs = provider.list_capabilities(kind="tool") + + assert specs == (provider.agent_capabilities["read_file"], provider.agent_capabilities["read"]) + assert specs[0].as_dict() == { + "name": "read_file", + "kind": "tool", + "aliases": ["read"], + "description": "Read a file.", + } + assert AgentCapabilitySpec("run").as_dict()["kind"] == "runtime" + + def test_capability_dispatch_reports_unknown_and_noncallable(self) -> None: + """Capability dispatch should fail clearly for invalid names and methods.""" + + class BrokenProvider(AgentCapabilityProviderMixin): + @agent_capability("broken") + def broken(self) -> str: + return "ok" + + provider = BrokenProvider() + + with pytest.raises(AttributeError, match="Unknown agent capability: missing"): + provider.call_capability("missing") + + provider.broken = "not callable" # type: ignore[method-assign] + + with pytest.raises(TypeError, match="not callable"): + provider.call_capability("broken") diff --git a/packages/agentic-fabric/tests/test_cli_commands.py b/packages/agentic-fabric/tests/test_cli_commands.py new file mode 100644 index 0000000..5a8b1fc --- /dev/null +++ b/packages/agentic-fabric/tests/test_cli_commands.py @@ -0,0 +1,337 @@ +"""Focused tests for CLI command helpers.""" + +from __future__ import annotations + +import json + +from pathlib import Path +from types import SimpleNamespace +from typing import Any +from unittest.mock import patch + +import pytest + +from agentic_fabric import main as cli + + +class FakeCliRunner: + """Small CLI runner stand-in used by command tests.""" + + def __init__(self, *, available: bool = True, result: str = "runner output") -> None: + self.config = SimpleNamespace( + name="Fake Runner", + description="Runs fake tasks", + install_cmd="install fake", + docs_url="https://example.com/fake", + ) + self.available = available + self.result = result + self.calls: list[dict[str, Any]] = [] + + def is_available(self) -> bool: + return self.available + + def get_required_env_vars(self) -> list[str]: + return ["FAKE_API_KEY"] + + def run(self, **kwargs: Any) -> str: + self.calls.append(kwargs) + return self.result + + +def test_cmd_list_text_displays_framework_marked_crews(capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(package=None, framework=None, json=False) + + with patch.object( + cli, + "list_crews", + return_value={"pkg": [{"name": "reviewer", "description": "reviews code", "required_framework": "crewai"}]}, + ): + cli.cmd_list(args) + + output = capsys.readouterr().out + assert "AVAILABLE CREWS" in output + assert "pkg" in output + assert "reviewer [crewai]: reviews code" in output + + +def test_cmd_list_json_flattens_packages(capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(package="pkg", framework="langgraph", json=True) + + with patch.object( + cli, + "list_crews", + return_value={"pkg": [{"name": "builder", "description": "builds", "required_framework": "langgraph"}]}, + ): + cli.cmd_list(args) + + assert json.loads(capsys.readouterr().out) == { + "crews": [ + { + "package": "pkg", + "name": "builder", + "description": "builds", + "required_framework": "langgraph", + } + ] + } + + +def test_cmd_run_json_success_auto_detects_framework( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + args = SimpleNamespace( + json=True, + runner=None, + package="pkg", + crew="reviewer", + file=None, + input="review this", + framework="auto", + ) + + with ( + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "get_crew_config", return_value={"name": "reviewer"}), + patch("agentic_fabric.core.decomposer.detect_framework", return_value="crewai"), + patch("agentic_fabric.core.decomposer.run_crew_auto", return_value="done") as run_crew_auto, + ): + cli.cmd_run(args) + + data = json.loads(capsys.readouterr().out) + assert data["success"] is True + assert data["output"] == "done" + assert data["framework_used"] == "crewai" + run_crew_auto.assert_called_once_with( + {"name": "reviewer"}, + inputs={"spec": "review this", "component_spec": "review this", "input": "review this"}, + framework=None, + ) + + +def test_cmd_run_json_uses_file_input_and_requested_framework( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + input_file = tmp_path / "task.txt" + input_file.write_text("from file", encoding="utf-8") + args = SimpleNamespace( + json=True, + runner=None, + package="pkg", + crew="reviewer", + file=str(input_file), + input=None, + framework="strands", + ) + + with ( + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".strands"}), + patch.object(cli, "get_crew_config", return_value={"name": "reviewer"}), + patch("agentic_fabric.core.decomposer.run_crew_auto", return_value="done") as run_crew_auto, + ): + cli.cmd_run(args) + + data = json.loads(capsys.readouterr().out) + assert data["success"] is True + assert data["framework_used"] == "strands" + assert run_crew_auto.call_args.kwargs["inputs"]["input"] == "from file" + assert run_crew_auto.call_args.kwargs["framework"] == "strands" + + +def test_cmd_run_json_reports_missing_package(capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace( + json=True, + runner=None, + package="missing", + crew="reviewer", + file=None, + input="task", + framework="auto", + ) + + with ( + patch.object(cli, "discover_packages", return_value={"known": Path(".crewai")}), + pytest.raises(SystemExit) as exc_info, + ): + cli.cmd_run(args) + + assert exc_info.value.code == 2 + data = json.loads(capsys.readouterr().out) + assert data["success"] is False + assert data["available_packages"] == ["known"] + + +def test_cmd_run_json_reports_execution_errors( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + args = SimpleNamespace( + json=True, + runner=None, + package="pkg", + crew="reviewer", + file=None, + input="task", + framework="auto", + ) + + with ( + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "get_crew_config", side_effect=ValueError("bad crew")), + pytest.raises(SystemExit) as exc_info, + ): + cli.cmd_run(args) + + assert exc_info.value.code == 1 + assert json.loads(capsys.readouterr().out)["error"] == "bad crew" + + +def test_cmd_info_json_success(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(package="pkg", crew="reviewer", json=True) + config = { + "description": "Reviews code", + "required_framework": "crewai", + "agents": {"reviewer": {"role": "Code Reviewer"}}, + "tasks": {"review": {"description": "Review the change"}}, + "knowledge_paths": ["knowledge"], + } + + with ( + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "get_crew_config", return_value=config), + ): + cli.cmd_info(args) + + data = json.loads(capsys.readouterr().out) + assert data["package"] == "pkg" + assert data["required_framework"] == "crewai" + assert data["agents"] == [{"name": "reviewer", "role": "Code Reviewer"}] + assert data["tasks"] == [{"name": "review", "description": "Review the change"}] + + +def test_cmd_info_json_reports_config_error(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(package="pkg", crew="missing", json=True) + + with ( + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "get_crew_config", side_effect=ValueError("missing crew")), + pytest.raises(SystemExit) as exc_info, + ): + cli.cmd_info(args) + + assert exc_info.value.code == 2 + assert json.loads(capsys.readouterr().out) == {"error": "missing crew"} + + +def test_cmd_list_runners_json_skips_bad_profiles(capsys: pytest.CaptureFixture[str]) -> None: + fake_runner = FakeCliRunner() + + def fake_get_cli_runner(profile: str) -> FakeCliRunner: + if profile == "bad": + raise RuntimeError("broken") + return fake_runner + + with ( + patch("agentic_fabric.core.decomposer.get_available_cli_runners", return_value=["good", "bad"]), + patch("agentic_fabric.core.decomposer.get_cli_runner", side_effect=fake_get_cli_runner), + ): + cli.cmd_list_runners(SimpleNamespace(json=True)) + + data = json.loads(capsys.readouterr().out) + assert data["runners"][0]["name"] == "good" + assert data["runners"][0]["available"] is True + assert data["runners"][0]["required_env"] == ["FAKE_API_KEY"] + + +def test_cmd_list_runners_reports_missing_profiles(capsys: pytest.CaptureFixture[str]) -> None: + with ( + patch("agentic_fabric.core.decomposer.get_available_cli_runners", side_effect=FileNotFoundError("no profiles")), + pytest.raises(SystemExit) as exc_info, + ): + cli.cmd_list_runners(SimpleNamespace(json=True)) + + assert exc_info.value.code == 2 + assert json.loads(capsys.readouterr().out) == {"error": "no profiles"} + + +def test_single_agent_json_success_uses_package_workdir( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + fake_runner = FakeCliRunner(result="fixed") + package_dir = tmp_path / "pkg" / ".crewai" + + args = SimpleNamespace( + json=True, + runner="fake", + input="fix it", + file=None, + package="pkg", + model="small", + auto_approve=False, + ) + + with ( + patch.object(cli, "discover_packages", return_value={"pkg": package_dir}), + patch("agentic_fabric.core.decomposer.get_cli_runner", return_value=fake_runner), + ): + cli._cmd_run_single_agent(args, use_json=True, start_time=0) + + data = json.loads(capsys.readouterr().out) + assert data["success"] is True + assert data["output"] == "fixed" + assert fake_runner.calls == [ + { + "task": "fix it", + "working_dir": str(package_dir.parent), + "auto_approve": False, + } + ] + + +def test_single_agent_json_requires_input(capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(json=True, runner="fake", input=None, file=None, package=None) + + with pytest.raises(SystemExit) as exc_info: + cli._cmd_run_single_agent(args, use_json=True, start_time=0) + + assert exc_info.value.code == 2 + assert json.loads(capsys.readouterr().out)["error"] == "No input provided. Use --input or --file" + + +def test_single_agent_json_reports_unavailable_runner(capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(json=True, runner="fake", input="task", file=None, package=None, model=None) + fake_runner = FakeCliRunner(available=False) + + with ( + patch("agentic_fabric.core.decomposer.get_cli_runner", return_value=fake_runner), + patch("agentic_fabric.core.decomposer.get_available_cli_runners", return_value=["other"]), + pytest.raises(SystemExit) as exc_info, + ): + cli._cmd_run_single_agent(args, use_json=True, start_time=0) + + assert exc_info.value.code == 2 + data = json.loads(capsys.readouterr().out) + assert data["success"] is False + assert data["available_runners"] == ["other"] + + +def test_single_agent_json_reports_runner_errors(capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(json=True, runner="fake", input="task", file=None, package=None, model=None) + fake_runner = FakeCliRunner() + + def broken_run(**kwargs: Any) -> str: + raise RuntimeError("runner failed") + + fake_runner.run = broken_run + + with ( + patch("agentic_fabric.core.decomposer.get_cli_runner", return_value=fake_runner), + pytest.raises(SystemExit) as exc_info, + ): + cli._cmd_run_single_agent(args, use_json=True, start_time=0) + + assert exc_info.value.code == 1 + assert json.loads(capsys.readouterr().out)["error"] == "runner failed" diff --git a/packages/agentic-fabric/tests/test_connector_builder_crew.py b/packages/agentic-fabric/tests/test_connector_builder_crew.py index d5f2116..8d50fe0 100644 --- a/packages/agentic-fabric/tests/test_connector_builder_crew.py +++ b/packages/agentic-fabric/tests/test_connector_builder_crew.py @@ -2,44 +2,107 @@ from __future__ import annotations -from unittest.mock import MagicMock, patch +import importlib +import sys +import types + +from typing import Any import pytest -pytest.importorskip("crewai", reason="crewai not installed") +class FakeAgent: + """Capture Agent constructor kwargs.""" + + calls: list[dict[str, Any]] = [] + + def __init__(self, **kwargs: Any) -> None: + self.kwargs = kwargs + self.calls.append(kwargs) + + +class FakeTask: + """Capture Task constructor kwargs.""" + + calls: list[dict[str, Any]] = [] + + def __init__(self, **kwargs: Any) -> None: + self.kwargs = kwargs + self.calls.append(kwargs) + + +class FakeCrew: + """Capture Crew constructor kwargs and kickoff input.""" + + calls: list[dict[str, Any]] = [] -from agentic_fabric.crews.connector_builder.connector_builder_crew import ConnectorBuilderCrew + def __init__(self, **kwargs: Any) -> None: + self.kwargs = kwargs + self.calls.append(kwargs) + self.kickoff_inputs: dict[str, Any] | None = None + def kickoff(self, inputs: dict[str, Any]) -> Any: + self.kickoff_inputs = inputs + return "Success" -@patch("agentic_fabric.crews.connector_builder.connector_builder_crew.Crew") -@patch("agentic_fabric.crews.connector_builder.connector_builder_crew.Agent") -@patch("agentic_fabric.crews.connector_builder.connector_builder_crew.Task") -@patch("agentic_fabric.crews.connector_builder.connector_builder_crew.resolve_tools") -def test_connector_builder_crew( - mock_resolve_tools: MagicMock, - mock_task: MagicMock, - mock_agent: MagicMock, - mock_crew: MagicMock, -): - """Tests that the ConnectorBuilderCrew initializes correctly and can be kicked off.""" - mock_resolve_tools.side_effect = lambda tools: [f"resolved:{tool}" for tool in tools] - # Test initialization - crew_instance = ConnectorBuilderCrew(output_dir="test_output") +def import_connector_builder_with_fake_crewai(monkeypatch: pytest.MonkeyPatch) -> Any: + """Import the connector builder module with fake CrewAI classes installed.""" + FakeAgent.calls.clear() + FakeTask.calls.clear() + FakeCrew.calls.clear() - assert mock_agent.call_count == 3 - assert mock_task.call_count == 3 - assert crew_instance.crew is not None - mock_crew.assert_called_once() - first_agent_kwargs = mock_agent.call_args_list[0][1] - assert first_agent_kwargs["tools"] == ["resolved:ScrapeWebsiteTool", "resolved:CrawlWebsiteTool"] + fake_crewai = types.ModuleType("crewai") + fake_crewai.Agent = FakeAgent + fake_crewai.Task = FakeTask + fake_crewai.Crew = FakeCrew + monkeypatch.setitem(sys.modules, "crewai", fake_crewai) + sys.modules.pop("agentic_fabric.crews.connector_builder.connector_builder_crew", None) + return importlib.import_module("agentic_fabric.crews.connector_builder.connector_builder_crew") - # Test kickoff - mock_crew_instance = mock_crew.return_value - mock_crew_instance.kickoff.return_value = "Success" + +def test_connector_builder_crew_initializes_and_kicks_off(monkeypatch: pytest.MonkeyPatch) -> None: + """ConnectorBuilderCrew should build configured agents/tasks and delegate kickoff.""" + module = import_connector_builder_with_fake_crewai(monkeypatch) + monkeypatch.setattr(module, "resolve_tools", lambda tools: [f"resolved:{tool}" for tool in tools]) + + crew_instance = module.ConnectorBuilderCrew(output_dir="test_output") + + assert len(FakeAgent.calls) == 3 + assert len(FakeTask.calls) == 3 + assert isinstance(crew_instance.crew, FakeCrew) + assert FakeAgent.calls[0]["tools"] == ["resolved:ScrapeWebsiteTool", "resolved:CrawlWebsiteTool"] + assert FakeAgent.calls[2]["tools"] == ["resolved:FileWriteTool"] + assert "test_output directory" in FakeTask.calls[2]["description"] + assert FakeCrew.calls[0]["agents"] == [ + crew_instance.doc_scraper, + crew_instance.api_analyzer, + crew_instance.code_generator, + ] + assert FakeCrew.calls[0]["tasks"] == [ + crew_instance.scrape_docs, + crew_instance.analyze_api, + crew_instance.generate_code, + ] result = crew_instance.kickoff(inputs={"url": "http://example.com"}) - mock_crew_instance.kickoff.assert_called_once_with(inputs={"url": "http://example.com"}) assert result == "Success" + assert crew_instance.crew.kickoff_inputs == {"url": "http://example.com"} + + +def test_connector_builder_kickoff_returns_raw_result(monkeypatch: pytest.MonkeyPatch) -> None: + """CrewAI raw results should be returned without string conversion.""" + module = import_connector_builder_with_fake_crewai(monkeypatch) + + class RawCrew(FakeCrew): + def kickoff(self, inputs: dict[str, Any]) -> Any: + self.kickoff_inputs = inputs + return types.SimpleNamespace(raw="raw success") + + monkeypatch.setattr(module, "Crew", RawCrew) + monkeypatch.setattr(module, "resolve_tools", lambda tools: []) + + crew_instance = module.ConnectorBuilderCrew() + + assert crew_instance.kickoff(inputs={"url": "http://example.com"}) == "raw success" diff --git a/packages/agentic-fabric/tests/test_core_runner.py b/packages/agentic-fabric/tests/test_core_runner.py new file mode 100644 index 0000000..36444e6 --- /dev/null +++ b/packages/agentic-fabric/tests/test_core_runner.py @@ -0,0 +1,92 @@ +"""Tests for the legacy CrewAI runner facade.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import pytest + +from agentic_fabric.core import runner as crew_runner + + +class FakeRawResult: + """Result object exposing CrewAI's raw output attribute.""" + + raw = "raw output" + + +class FakeStringResult: + """Result object that relies on string conversion.""" + + def __str__(self) -> str: + return "string output" + + +class FakeCrew: + """Minimal crew object with kickoff capture.""" + + def __init__(self, result: Any) -> None: + self.result = result + self.inputs: dict[str, Any] | None = None + + def kickoff(self, inputs: dict[str, Any]) -> Any: + self.inputs = inputs + return self.result + + +def test_run_crew_discovers_package_loads_config_and_returns_raw(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + crewai_dir = tmp_path / "pkg" / ".crewai" + fake_crew = FakeCrew(FakeRawResult()) + calls: list[tuple[str, Any]] = [] + + monkeypatch.setattr(crew_runner, "discover_packages", lambda workspace_root: {"pkg": crewai_dir}) + + def fake_get_crew_config(path: Path, crew_name: str) -> dict[str, Any]: + calls.append(("config", path, crew_name)) + return {"name": crew_name} + + def fake_load_crew_from_config(config: dict[str, Any]) -> FakeCrew: + calls.append(("load", config)) + return fake_crew + + monkeypatch.setattr(crew_runner, "get_crew_config", fake_get_crew_config) + monkeypatch.setattr(crew_runner, "load_crew_from_config", fake_load_crew_from_config) + + result = crew_runner.run_crew("pkg", "builder", inputs={"topic": "tests"}, workspace_root=tmp_path) + + assert result == "raw output" + assert fake_crew.inputs == {"topic": "tests"} + assert calls == [("config", crewai_dir, "builder"), ("load", {"name": "builder"})] + + +def test_run_crew_reports_available_packages(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(crew_runner, "discover_packages", lambda workspace_root: {"known": Path(".crewai")}) + + with pytest.raises(ValueError, match=r"Package 'missing' not found. Available: \['known'\]"): + crew_runner.run_crew("missing", "builder") + + +def test_run_crew_defaults_inputs_to_empty_dict(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + fake_crew = FakeCrew(FakeStringResult()) + + monkeypatch.setattr(crew_runner, "discover_packages", lambda workspace_root: {"pkg": tmp_path}) + monkeypatch.setattr(crew_runner, "get_crew_config", lambda path, crew_name: {"name": crew_name}) + monkeypatch.setattr(crew_runner, "load_crew_from_config", lambda config: fake_crew) + + result = crew_runner.run_crew("pkg", "builder") + + assert result == "string output" + assert fake_crew.inputs == {} + + +def test_run_crew_from_path_loads_direct_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + fake_crew = FakeCrew(FakeStringResult()) + + monkeypatch.setattr(crew_runner, "get_crew_config", lambda path, crew_name: {"path": path, "name": crew_name}) + monkeypatch.setattr(crew_runner, "load_crew_from_config", lambda config: fake_crew) + + result = crew_runner.run_crew_from_path(tmp_path / ".crewai", "builder", inputs={"x": 1}) + + assert result == "string output" + assert fake_crew.inputs == {"x": 1} diff --git a/packages/agentic-fabric/tests/test_file_tool_paths.py b/packages/agentic-fabric/tests/test_file_tool_paths.py index 8c81dec..3f9c1e7 100644 --- a/packages/agentic-fabric/tests/test_file_tool_paths.py +++ b/packages/agentic-fabric/tests/test_file_tool_paths.py @@ -48,3 +48,121 @@ def test_allowed_write_path_requires_directory_boundary(monkeypatch: pytest.Monk assert file_tools._is_allowed_write_path("src/ecs/component.ts") assert not file_tools._is_allowed_write_path("src/ecsnot/component.ts") + + +def test_workspace_root_resolution_uses_workspace_env_and_cwd( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Workspace discovery should fall back from package roots to env vars and cwd.""" + file_tools = import_file_tools_with_fake_crewai(monkeypatch) + workspace_root = tmp_path / "workspace" + target_package = workspace_root / "packages" / "otterfall" + target_package.mkdir(parents=True) + monkeypatch.setattr(file_tools, "_find_workspace_root", lambda: workspace_root) + + assert file_tools.get_workspace_root() == target_package + + env_root = tmp_path / "env-root" + env_root.mkdir() + monkeypatch.setattr(file_tools, "_find_workspace_root", lambda: None) + monkeypatch.setenv("OTTERFALL_ROOT", str(env_root)) + + assert file_tools.get_workspace_root("otterfall") == env_root + + monkeypatch.delenv("OTTERFALL_ROOT") + monkeypatch.chdir(tmp_path) + + assert file_tools.get_workspace_root("otterfall") == tmp_path + + +def test_clean_relative_path_rejects_invalid_values(monkeypatch: pytest.MonkeyPatch) -> None: + """Relative path cleaning should reject empty, absolute, and traversal paths.""" + file_tools = import_file_tools_with_fake_crewai(monkeypatch) + + for value in ("", "/tmp/file.ts", "src/../secret.ts"): + with pytest.raises(ValueError, match="Path traversal not allowed"): + file_tools._clean_relative_path(value) + + +def test_file_tools_write_read_and_list_allowed_workspace(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """The CrewAI file tools should operate inside the resolved workspace root.""" + file_tools = import_file_tools_with_fake_crewai(monkeypatch) + workspace_root = tmp_path / "workspace" + workspace_root.mkdir() + monkeypatch.setattr(file_tools, "get_workspace_root", lambda: workspace_root) + + write_result = file_tools.GameCodeWriterTool()._run("src/ecs/data/species.ts", "export const species = [];") + read_result = file_tools.GameCodeReaderTool()._run("src/ecs/data/species.ts") + list_result = file_tools.DirectoryListTool()._run("src/ecs/data") + + assert "Successfully wrote" in write_result + assert read_result == "export const species = [];" + assert "species.ts" in list_result + + +def test_file_tools_report_common_user_errors(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Runtime tool errors should be returned as agent-readable strings.""" + file_tools = import_file_tools_with_fake_crewai(monkeypatch) + workspace_root = tmp_path / "workspace" + workspace_root.mkdir() + monkeypatch.setattr(file_tools, "get_workspace_root", lambda: workspace_root) + + bad_write_dir = file_tools.GameCodeWriterTool()._run("src/forbidden/file.ts", "") + bad_write_ext = file_tools.GameCodeWriterTool()._run("src/ecs/data/file.py", "") + missing_read = file_tools.GameCodeReaderTool()._run("src/ecs/data/missing.ts") + missing_dir = file_tools.DirectoryListTool()._run("src/ecs/data") + + assert "not in an allowed directory" in bad_write_dir + assert "Extension '.py' not allowed" in bad_write_ext + assert "File not found" in missing_read + assert "Directory not found" in missing_dir + + +def test_file_tools_report_reader_and_lister_edge_cases(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Reader and lister should report directories, large files, files, and empty directories.""" + file_tools = import_file_tools_with_fake_crewai(monkeypatch) + workspace_root = tmp_path / "workspace" + data_dir = workspace_root / "src" / "ecs" / "data" + data_dir.mkdir(parents=True) + monkeypatch.setattr(file_tools, "get_workspace_root", lambda: workspace_root) + + (data_dir / "folder").mkdir() + large_file = data_dir / "large.ts" + large_file.write_text("x" * 100_001, encoding="utf-8") + empty_dir = data_dir / "empty" + empty_dir.mkdir() + (data_dir / ".hidden.ts").write_text("hidden", encoding="utf-8") + (data_dir / "visible.ts").write_text("visible", encoding="utf-8") + + assert "Path is not a file" in file_tools.GameCodeReaderTool()._run("src/ecs/data/folder") + assert "File too large" in file_tools.GameCodeReaderTool()._run("src/ecs/data/large.ts") + assert "Path is not a directory" in file_tools.DirectoryListTool()._run("src/ecs/data/visible.ts") + assert file_tools.DirectoryListTool()._run("src/ecs/data/empty") == "Directory src/ecs/data/empty is empty" + + list_result = file_tools.DirectoryListTool()._run("src/ecs/data") + assert "visible.ts" in list_result + assert ".hidden.ts" not in list_result + + +def test_file_tools_convert_permission_and_unexpected_errors(monkeypatch: pytest.MonkeyPatch) -> None: + """Tool exceptions should be converted to agent-readable strings.""" + file_tools = import_file_tools_with_fake_crewai(monkeypatch) + + def raise_permission(path_value: str) -> tuple[str, Path]: + raise PermissionError + + monkeypatch.setattr(file_tools, "_resolve_workspace_path", raise_permission) + + assert "Permission denied writing" in file_tools.GameCodeWriterTool()._run("src/ecs/data/file.ts", "") + assert "Permission denied reading" in file_tools.GameCodeReaderTool()._run("src/ecs/data/file.ts") + assert "Permission denied accessing" in file_tools.DirectoryListTool()._run("src/ecs/data") + + def raise_unexpected(path_value: str) -> tuple[str, Path]: + raise RuntimeError("boom") + + monkeypatch.setattr(file_tools, "_resolve_workspace_path", raise_unexpected) + + assert "Error writing file: boom" in file_tools.GameCodeWriterTool()._run("src/ecs/data/file.ts", "") + assert "Error reading file: boom" in file_tools.GameCodeReaderTool()._run("src/ecs/data/file.ts") + assert "Error listing directory: boom" in file_tools.DirectoryListTool()._run("src/ecs/data") diff --git a/packages/agentic-fabric/tests/test_llm_config.py b/packages/agentic-fabric/tests/test_llm_config.py new file mode 100644 index 0000000..5955b5a --- /dev/null +++ b/packages/agentic-fabric/tests/test_llm_config.py @@ -0,0 +1,133 @@ +"""Tests for LLM provider selection helpers.""" + +from __future__ import annotations + +from typing import Any + +import pytest + +from agentic_fabric.config import llm as llm_config +from agentic_fabric.config.llm import LLMProvider + + +class FakeLLM: + """Small stand-in for CrewAI's LLM wrapper.""" + + def __init__(self, **kwargs: Any) -> None: + self.kwargs = kwargs + + +def clear_llm_env(monkeypatch: pytest.MonkeyPatch) -> None: + """Remove provider credentials installed by the autouse test fixture.""" + monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) + monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) + + +def enable_fake_llm(monkeypatch: pytest.MonkeyPatch) -> None: + """Install the fake LLM constructor for provider-selection tests.""" + monkeypatch.setattr(llm_config, "LLM", FakeLLM) + + +def test_get_llm_returns_none_when_constructor_unavailable(monkeypatch: pytest.MonkeyPatch) -> None: + clear_llm_env(monkeypatch) + monkeypatch.setattr(llm_config, "LLM", None) + + assert llm_config.get_llm() is None + + +def test_get_llm_returns_none_without_provider_keys(monkeypatch: pytest.MonkeyPatch) -> None: + clear_llm_env(monkeypatch) + enable_fake_llm(monkeypatch) + + assert llm_config.get_llm() is None + + +def test_get_llm_prefers_anthropic_for_direct_models(monkeypatch: pytest.MonkeyPatch) -> None: + enable_fake_llm(monkeypatch) + monkeypatch.setenv("ANTHROPIC_API_KEY", "anthropic-key") + monkeypatch.setenv("OPENROUTER_API_KEY", "openrouter-key") + + result = llm_config.get_llm("claude-sonnet-4-20250514", temperature=0.2) + + assert isinstance(result, FakeLLM) + assert result.kwargs == { + "model": "claude-sonnet-4-20250514", + "api_key": "anthropic-key", + "temperature": 0.2, + } + + +def test_get_llm_can_force_openrouter_and_normalizes_model(monkeypatch: pytest.MonkeyPatch) -> None: + clear_llm_env(monkeypatch) + enable_fake_llm(monkeypatch) + monkeypatch.setenv("OPENROUTER_API_KEY", "openrouter-key") + + result = llm_config.get_llm( + "claude-sonnet-4-20250514", + temperature=0.4, + provider=LLMProvider.OPENROUTER, + ) + + assert isinstance(result, FakeLLM) + assert result.kwargs == { + "model": "openrouter/auto", + "api_key": "openrouter-key", + "base_url": "https://openrouter.ai/api/v1", + "temperature": 0.4, + } + + +def test_get_llm_preserves_openrouter_model_in_auto_detection(monkeypatch: pytest.MonkeyPatch) -> None: + enable_fake_llm(monkeypatch) + monkeypatch.setenv("ANTHROPIC_API_KEY", "anthropic-key") + monkeypatch.setenv("OPENROUTER_API_KEY", "openrouter-key") + + result = llm_config.get_llm("openrouter/anthropic/claude-haiku-4.5") + + assert isinstance(result, FakeLLM) + assert result.kwargs["model"] == "openrouter/anthropic/claude-haiku-4.5" + assert result.kwargs["api_key"] == "openrouter-key" + assert result.kwargs["base_url"] == "https://openrouter.ai/api/v1" + + +def test_forced_providers_require_matching_key(monkeypatch: pytest.MonkeyPatch) -> None: + clear_llm_env(monkeypatch) + enable_fake_llm(monkeypatch) + monkeypatch.setenv("ANTHROPIC_API_KEY", "anthropic-key") + + assert llm_config.get_llm(provider=LLMProvider.OPENROUTER) is None + + monkeypatch.delenv("ANTHROPIC_API_KEY") + monkeypatch.setenv("OPENROUTER_API_KEY", "openrouter-key") + + assert llm_config.get_llm(provider=LLMProvider.ANTHROPIC) is None + + +def test_get_llm_or_raise_reports_install_guidance(monkeypatch: pytest.MonkeyPatch) -> None: + clear_llm_env(monkeypatch) + enable_fake_llm(monkeypatch) + + with pytest.raises(ValueError, match="ANTHROPIC_API_KEY or OPENROUTER_API_KEY"): + llm_config.get_llm_or_raise() + + +def test_task_helpers_use_declared_configs(monkeypatch: pytest.MonkeyPatch) -> None: + calls: list[tuple[str, float]] = [] + + def fake_get_llm(model: str, temperature: float, provider: LLMProvider | None = None) -> str: + calls.append((model, temperature)) + assert provider is None + return "llm" + + monkeypatch.setattr(llm_config, "get_llm", fake_get_llm) + + assert llm_config.get_llm_for_task("code") == "llm" + assert calls == [(llm_config.LLM_CONFIGS["code"].model, llm_config.LLM_CONFIGS["code"].temperature)] + assert llm_config.get_reasoning_llm() == "llm" + assert llm_config.get_creative_llm() == "llm" + assert llm_config.get_code_llm() == "llm" + + +def test_get_llm_for_task_rejects_unknown_task() -> None: + with pytest.raises(ValueError, match="Unknown task type: unknown"): + llm_config.get_llm_for_task("unknown") diff --git a/packages/agentic-fabric/tests/test_loader_unit.py b/packages/agentic-fabric/tests/test_loader_unit.py new file mode 100644 index 0000000..caca732 --- /dev/null +++ b/packages/agentic-fabric/tests/test_loader_unit.py @@ -0,0 +1,210 @@ +"""CrewAI loader tests that do not require the optional CrewAI package.""" + +from __future__ import annotations + +import importlib +import sys +import types + +from collections.abc import Generator +from pathlib import Path +from typing import Any + +import pytest + + +class FakeAgent: + """Capture CrewAI Agent constructor kwargs.""" + + def __init__(self, **kwargs: Any) -> None: + self.kwargs = kwargs + + +class FakeTask: + """Capture CrewAI Task constructor kwargs.""" + + def __init__(self, **kwargs: Any) -> None: + self.kwargs = kwargs + + +class FakeCrew: + """Capture CrewAI Crew constructor kwargs.""" + + def __init__(self, **kwargs: Any) -> None: + self.kwargs = kwargs + + +class FakeProcess: + """Small stand-in for CrewAI Process.""" + + sequential = "sequential" + + +class FakeKnowledgeSource: + """Capture knowledge source file paths.""" + + def __init__(self, file_paths: list[str]) -> None: + self.file_paths = file_paths + + +@pytest.fixture +def loader_with_fake_crewai(monkeypatch: pytest.MonkeyPatch) -> Generator[Any, None, None]: + """Import the loader with fake CrewAI modules installed.""" + fake_crewai = types.ModuleType("crewai") + fake_crewai.Agent = FakeAgent + fake_crewai.Task = FakeTask + fake_crewai.Crew = FakeCrew + fake_crewai.Process = FakeProcess + + fake_tools = types.ModuleType("crewai.tools") + + class BaseTool: + """Minimal stand-in for crewai.tools.BaseTool.""" + + fake_tools.BaseTool = BaseTool + + fake_knowledge = types.ModuleType("crewai.knowledge") + fake_knowledge.__path__ = [] + fake_source = types.ModuleType("crewai.knowledge.source") + fake_source.__path__ = [] + fake_text_source = types.ModuleType("crewai.knowledge.source.text_file_knowledge_source") + fake_text_source.TextFileKnowledgeSource = FakeKnowledgeSource + + for module_name, module in { + "crewai": fake_crewai, + "crewai.tools": fake_tools, + "crewai.knowledge": fake_knowledge, + "crewai.knowledge.source": fake_source, + "crewai.knowledge.source.text_file_knowledge_source": fake_text_source, + }.items(): + monkeypatch.setitem(sys.modules, module_name, module) + + sys.modules.pop("agentic_fabric.core.loader", None) + sys.modules.pop("agentic_fabric.tools.file_tools", None) + loader = importlib.import_module("agentic_fabric.core.loader") + + yield loader + + sys.modules.pop("agentic_fabric.core.loader", None) + sys.modules.pop("agentic_fabric.tools.file_tools", None) + + +def test_load_knowledge_sources_reads_supported_nonempty_files( + loader_with_fake_crewai: Any, + tmp_path: Path, +) -> None: + knowledge_dir = tmp_path / "knowledge" + knowledge_dir.mkdir() + note = knowledge_dir / "note.md" + note.write_text("# Useful context", encoding="utf-8") + (knowledge_dir / "empty.py").write_text(" ", encoding="utf-8") + (knowledge_dir / "ignored.txt").write_text("not supported", encoding="utf-8") + + sources = loader_with_fake_crewai.load_knowledge_sources([knowledge_dir, tmp_path / "missing"]) + + assert len(sources) == 1 + assert sources[0].file_paths == [str(note)] + + +def test_load_knowledge_sources_logs_unreadable_files( + loader_with_fake_crewai: Any, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture, +) -> None: + knowledge_dir = tmp_path / "knowledge" + knowledge_dir.mkdir() + bad_file = knowledge_dir / "bad.md" + bad_file.write_text("content", encoding="utf-8") + + def broken_read_text(self: Path, *args: Any, **kwargs: Any) -> str: + if self == bad_file: + raise OSError("boom") + return original_read_text(self, *args, **kwargs) + + original_read_text = Path.read_text + monkeypatch.setattr(Path, "read_text", broken_read_text) + + assert loader_with_fake_crewai.load_knowledge_sources([knowledge_dir]) == [] + assert "Could not load knowledge source" in caplog.text + + +def test_create_agent_and_task_from_config(loader_with_fake_crewai: Any, monkeypatch: pytest.MonkeyPatch) -> None: + sentinel_llm = object() + monkeypatch.setattr("agentic_fabric.config.llm.get_llm", lambda: sentinel_llm) + + agent = loader_with_fake_crewai.create_agent_from_config("writer", {"goal": "write"}, tools=["tool"]) + task = loader_with_fake_crewai.create_task_from_config("draft", {"description": "draft"}, agent) + + assert isinstance(agent, FakeAgent) + assert agent.kwargs["role"] == "writer" + assert agent.kwargs["goal"] == "write" + assert agent.kwargs["backstory"] == "" + assert agent.kwargs["llm"] is sentinel_llm + assert agent.kwargs["tools"] == ["tool"] + assert agent.kwargs["allow_delegation"] is False + assert task.kwargs["description"] == "draft" + assert task.kwargs["expected_output"] == "" + assert task.kwargs["agent"] is agent + + +def test_load_crew_from_config_resolves_tools_and_fallbacks( + loader_with_fake_crewai: Any, + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr("agentic_fabric.config.llm.get_llm", lambda: None) + monkeypatch.setattr(loader_with_fake_crewai, "load_knowledge_sources", lambda paths: ["knowledge"]) + monkeypatch.setattr( + loader_with_fake_crewai, + "resolve_tools", + lambda tool_names: ["resolved-tool"] if tool_names else [], + ) + + crew = loader_with_fake_crewai.load_crew_from_config( + { + "agents": { + "tool_user": {"role": "Tool User", "tools": ["registered"]}, + "engineer": {"role": "Engineer"}, + "researcher": {"role": "Researcher"}, + }, + "tasks": { + "tool_task": {"description": "use a tool", "agent": "tool_user"}, + "build_task": {"description": "build", "agent": "engineer"}, + "research_task": {"description": "read", "agent": "researcher"}, + }, + "knowledge_paths": [Path("knowledge")], + } + ) + + assert isinstance(crew, FakeCrew) + assert crew.kwargs["process"] == "sequential" + assert crew.kwargs["planning"] is True + assert crew.kwargs["memory"] is True + assert crew.kwargs["knowledge_sources"] == ["knowledge"] + + agents = crew.kwargs["agents"] + assert [agent.kwargs["role"] for agent in agents] == ["Tool User", "Engineer", "Researcher"] + assert agents[0].kwargs["tools"] == ["resolved-tool"] + assert len(agents[1].kwargs["tools"]) == 3 + assert len(agents[2].kwargs["tools"]) == 2 + assert len(crew.kwargs["tasks"]) == 3 + + +def test_load_crew_from_config_requires_task_agent(loader_with_fake_crewai: Any) -> None: + with pytest.raises(ValueError, match="missing an 'agent' assignment"): + loader_with_fake_crewai.load_crew_from_config( + { + "agents": {"writer": {"role": "Writer"}}, + "tasks": {"draft": {"description": "draft"}}, + } + ) + + +def test_load_crew_from_config_requires_existing_agent(loader_with_fake_crewai: Any) -> None: + with pytest.raises(ValueError, match="Agent 'missing' for task 'draft' not found"): + loader_with_fake_crewai.load_crew_from_config( + { + "agents": {"writer": {"role": "Writer"}}, + "tasks": {"draft": {"description": "draft", "agent": "missing"}}, + } + ) diff --git a/packages/agentic-fabric/tests/test_runtime_registry.py b/packages/agentic-fabric/tests/test_runtime_registry.py new file mode 100644 index 0000000..e060075 --- /dev/null +++ b/packages/agentic-fabric/tests/test_runtime_registry.py @@ -0,0 +1,115 @@ +"""Tests for the optional runtime registry.""" + +from __future__ import annotations + +import types + +from typing import Any +from unittest.mock import MagicMock + +import pytest + +from agentic_fabric.runners import registry +from agentic_fabric.runners.registry import RuntimeSpec, RuntimeUnavailableError + + +class FakeRunner: + """Runner class loaded through the registry in tests.""" + + +def install_fake_registry(monkeypatch: pytest.MonkeyPatch, spec: RuntimeSpec) -> None: + """Replace the global runtime registry for one test.""" + monkeypatch.setattr(registry, "_RUNTIME_SPECS", {spec.name: spec}) + monkeypatch.setattr(registry, "_AVAILABILITY_CACHE", {}) + + +def test_runtime_spec_serializes_metadata() -> None: + spec = RuntimeSpec( + name="fake", + import_name="fake_runtime", + runner_module="fake_runner", + runner_class="FakeRunner", + install="install fake", + extra="fake", + description="Fake runtime.", + ) + + assert spec.as_dict(available=True) == { + "name": "fake", + "import_name": "fake_runtime", + "runner": "fake_runner:FakeRunner", + "install": "install fake", + "extra": "fake", + "available": True, + "description": "Fake runtime.", + } + + +def test_register_runtime_replaces_spec_and_clears_cache(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(registry, "_RUNTIME_SPECS", {}) + monkeypatch.setattr(registry, "_AVAILABILITY_CACHE", {"fake": True}) + spec = RuntimeSpec("fake", "fake_runtime", "fake_runner", "FakeRunner", "install fake", "fake") + + registry.register_runtime(spec) + + assert registry.runtime_names() == ["fake"] + assert registry.runtime_specs()["fake"] is spec + assert "fake" not in registry._AVAILABILITY_CACHE + + with pytest.raises(TypeError): + registry.runtime_specs()["other"] = spec # type: ignore[index] + + +def test_unknown_runtime_reports_options(monkeypatch: pytest.MonkeyPatch) -> None: + spec = RuntimeSpec("known", "known_runtime", "known_runner", "KnownRunner", "install known", "known") + install_fake_registry(monkeypatch, spec) + + with pytest.raises(ValueError, match=r"Unknown runtime: missing. Options: \[known\]"): + registry.get_runtime_spec("missing") + + +def test_runtime_availability_caches_import_success_and_unknown(monkeypatch: pytest.MonkeyPatch) -> None: + spec = RuntimeSpec("fake", "fake_runtime", "fake_runner", "FakeRunner", "install fake", "fake") + install_fake_registry(monkeypatch, spec) + mock_import = MagicMock(return_value=types.ModuleType("fake_runtime")) + monkeypatch.setattr(registry.importlib, "import_module", mock_import) + + assert registry.is_runtime_available("unknown") is False + assert registry.is_runtime_available("fake") is True + assert registry.is_runtime_available("fake") is True + mock_import.assert_called_once_with("fake_runtime") + assert registry.available_runtimes() == ["fake"] + + +def test_runtime_info_and_require_runtime_report_unavailable(monkeypatch: pytest.MonkeyPatch) -> None: + spec = RuntimeSpec("fake", "missing_runtime", "fake_runner", "FakeRunner", "install fake", "fake") + install_fake_registry(monkeypatch, spec) + monkeypatch.setattr(registry.importlib, "import_module", MagicMock(side_effect=ImportError("missing"))) + + assert registry.runtime_info("fake")["available"] is False + assert registry.runtime_info() == [spec.as_dict(available=False)] + + with pytest.raises(RuntimeUnavailableError) as exc_info: + registry.require_runtime("fake") + + assert exc_info.value.runtime == "fake" + assert exc_info.value.install == "install fake" + + +def test_load_runner_imports_runner_class(monkeypatch: pytest.MonkeyPatch) -> None: + spec = RuntimeSpec("fake", "fake_runtime", "fake_runner", "FakeRunner", "install fake", "fake") + install_fake_registry(monkeypatch, spec) + fake_runner_module = types.ModuleType("fake_runner") + fake_runner_module.FakeRunner = FakeRunner + + def fake_import_module(name: str) -> Any: + if name == "fake_runtime": + return types.ModuleType("fake_runtime") + if name == "fake_runner": + return fake_runner_module + raise ImportError(name) + + monkeypatch.setattr(registry.importlib, "import_module", fake_import_module) + + assert isinstance(registry.load_runner("fake"), FakeRunner) + assert registry.install_command("fake") == "install fake" diff --git a/packages/agentic-fabric/tests/test_tool_adapters.py b/packages/agentic-fabric/tests/test_tool_adapters.py index 641f653..d362fe5 100644 --- a/packages/agentic-fabric/tests/test_tool_adapters.py +++ b/packages/agentic-fabric/tests/test_tool_adapters.py @@ -7,9 +7,17 @@ from types import ModuleType, SimpleNamespace from unittest.mock import MagicMock, patch +import pytest + from pydantic import BaseModel -from agentic_fabric.tools.adapters import resolve_langgraph_tools, resolve_strands_tools +from agentic_fabric.tools.adapters import ( + _build_runner, + _invoke_tool, + _tool_name, + resolve_langgraph_tools, + resolve_strands_tools, +) class WriteFileArgs(BaseModel): @@ -33,6 +41,15 @@ def _run(self, **kwargs: str) -> str: return f"wrote:{kwargs['file_path']}" +class CallableTool: + """Callable tool object without a CrewAI _run method.""" + + __name__ = "1 callable tool" + + def __call__(self, **kwargs: str) -> str: + return f"called:{kwargs['value']}" + + class FakeStructuredTool: """Minimal LangChain-style tool wrapper used for adapter tests.""" @@ -95,6 +112,21 @@ def wrapped(**kwargs: str) -> str: class TestLangGraphToolAdapters: """Tests for LangGraph configured-tool wrapping.""" + def test_empty_and_unresolved_tools_return_empty(self) -> None: + """Empty declarations and unresolved names should not require LangChain.""" + assert resolve_langgraph_tools([]) == [] + + with patch("agentic_fabric.tools.adapters.resolve_tools", return_value=[]): + assert resolve_langgraph_tools(["missing"]) == [] + + @patch("agentic_fabric.tools.adapters.resolve_tools") + def test_missing_langchain_dependency_skips_tools(self, mock_resolve_tools: MagicMock) -> None: + """Missing LangChain should be reported as unavailable adaptation.""" + mock_resolve_tools.return_value = [DummyTool()] + + with patch.dict(sys.modules, {"langchain_core.tools": None}): + assert resolve_langgraph_tools(["FileWriteTool"]) == [] + @patch("agentic_fabric.tools.adapters.resolve_tools") def test_wraps_resolved_tools_with_structured_tool(self, mock_resolve_tools: MagicMock) -> None: """Resolved tools should be adapted into LangChain-compatible wrappers.""" @@ -134,6 +166,21 @@ def test_preserves_existing_langgraph_tool_objects(self, mock_resolve_tools: Mag class TestStrandsToolAdapters: """Tests for Strands configured-tool wrapping.""" + def test_empty_and_unresolved_tools_return_empty(self) -> None: + """Empty declarations and unresolved names should not require Strands.""" + assert resolve_strands_tools([]) == [] + + with patch("agentic_fabric.tools.adapters.resolve_tools", return_value=[]): + assert resolve_strands_tools(["missing"]) == [] + + @patch("agentic_fabric.tools.adapters.resolve_tools") + def test_missing_strands_dependency_skips_tools(self, mock_resolve_tools: MagicMock) -> None: + """Missing Strands should be reported as unavailable adaptation.""" + mock_resolve_tools.return_value = [DummyTool()] + + with patch.dict(sys.modules, {"strands": None}): + assert resolve_strands_tools(["FileWriteTool"]) == [] + @patch("agentic_fabric.tools.adapters.resolve_tools") def test_wraps_resolved_tools_with_strands_decorator(self, mock_resolve_tools: MagicMock) -> None: """Resolved tools should be adapted into callable Strands tool wrappers.""" @@ -167,3 +214,22 @@ def test_preserves_existing_strands_tool_objects(self, mock_resolve_tools: Magic adapted = resolve_strands_tools(["get_secret_number"]) assert adapted == [existing_tool] + + +class TestToolAdapterHelpers: + """Tests for plain adapter helper functions.""" + + def test_tool_names_are_framework_safe(self) -> None: + assert _tool_name(CallableTool()) == "tool_1_callable_tool" + assert _tool_name(object()) == "object" + + def test_build_runner_invokes_callable_tools(self) -> None: + runner = _build_runner(CallableTool(), "callable_tool", "Call a tool.") + + assert runner(value="x") == "called:x" + assert runner.__name__ == "callable_tool" + assert runner.__doc__ == "Call a tool." + + def test_invoke_tool_rejects_noncallable_objects(self) -> None: + with pytest.raises(TypeError, match="not callable"): + _invoke_tool(object(), {}) diff --git a/packages/agentic-fabric/tests/test_tools_init.py b/packages/agentic-fabric/tests/test_tools_init.py new file mode 100644 index 0000000..6a5aab4 --- /dev/null +++ b/packages/agentic-fabric/tests/test_tools_init.py @@ -0,0 +1,89 @@ +"""Tests for lazy tool package exports.""" + +from __future__ import annotations + +import builtins + +from typing import Any + +import pytest + +from agentic_fabric import tools + + +class FakeTool: + """Constructible fake tool used by lazy loader tests.""" + + +def test_getattr_lazily_loads_declared_tool(monkeypatch: pytest.MonkeyPatch) -> None: + calls: list[tuple[str, str]] = [] + + def fake_load_attr(module_name: str, attr_name: str) -> type[FakeTool]: + calls.append((module_name, attr_name)) + return FakeTool + + monkeypatch.setattr(tools, "_load_attr", fake_load_attr) + + assert tools.__getattr__("GameCodeReaderTool") is FakeTool + assert tools.__getattr__("ScrapeWebsiteTool") is FakeTool + assert calls == [ + ("agentic_fabric.tools.file_tools", "GameCodeReaderTool"), + ("agentic_fabric.tools.scraping_tools", "ScrapeWebsiteTool"), + ] + + +def test_getattr_rejects_unknown_name() -> None: + with pytest.raises(AttributeError, match="does-not-exist"): + tools.__getattr__("does-not-exist") + + +def test_tool_collection_helpers_instantiate_lazy_tools(monkeypatch: pytest.MonkeyPatch) -> None: + def fake_load_attr(module_name: str, attr_name: str) -> type[FakeTool]: + assert module_name in {"agentic_fabric.tools.file_tools", "agentic_fabric.tools.scraping_tools"} + assert attr_name.endswith("Tool") + return FakeTool + + monkeypatch.setattr(tools, "_load_attr", fake_load_attr) + + file_tools = tools.get_file_tools() + scraping_tools = tools.get_scraping_tools() + + assert len(file_tools) == 3 + assert all(isinstance(tool, FakeTool) for tool in file_tools) + assert len(scraping_tools) == 2 + assert all(isinstance(tool, FakeTool) for tool in scraping_tools) + + +def test_get_all_tools_suppresses_unavailable_optional_tools(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(tools, "get_file_tools", lambda: ["file"]) + monkeypatch.setattr(tools, "get_scraping_tools", lambda: (_ for _ in ()).throw(ImportError("missing scraping"))) + real_import = builtins.__import__ + + def fake_import(name: str, *args: Any, **kwargs: Any) -> Any: + if name == "mesh_toolkit.agent_tools.crewai": + raise ImportError("missing meshy") + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", fake_import) + + assert tools.get_all_tools() == ["file"] + + +def test_get_all_tools_includes_meshy_tools_when_available(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(tools, "get_file_tools", lambda: ["file"]) + monkeypatch.setattr(tools, "get_scraping_tools", lambda: ["scrape"]) + real_import = builtins.__import__ + + class FakeMeshModule: + @staticmethod + def get_tools() -> list[str]: + return ["mesh"] + + def fake_import(name: str, *args: Any, **kwargs: Any) -> Any: + if name == "mesh_toolkit.agent_tools.crewai": + return FakeMeshModule + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", fake_import) + + assert tools.get_all_tools() == ["file", "scrape", "mesh"] From 469bcc4a911490800cb4d159d069b484ee2bcd72 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 00:42:38 -0500 Subject: [PATCH 06/35] fix: read crew yaml as utf8 --- .../src/agentic_fabric/core/discovery.py | 4 +-- .../tests/test_yaml_config_errors.py | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py index 4940be0..9f4a79e 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py @@ -233,8 +233,8 @@ def get_crew_config(config_dir: Path, crew_name: str) -> dict: agents_path = _resolve_config_path(config_dir, crew_config["agents"]) tasks_path = _resolve_config_path(config_dir, crew_config["tasks"]) - agents = yaml.safe_load(agents_path.read_text()) if agents_path.exists() else {} - tasks = yaml.safe_load(tasks_path.read_text()) if tasks_path.exists() else {} + agents = yaml.safe_load(agents_path.read_text(encoding="utf-8")) if agents_path.exists() else {} + tasks = yaml.safe_load(tasks_path.read_text(encoding="utf-8")) if tasks_path.exists() else {} # Resolve knowledge paths knowledge_paths = [] diff --git a/packages/agentic-fabric/tests/test_yaml_config_errors.py b/packages/agentic-fabric/tests/test_yaml_config_errors.py index caf7ab1..7c6db35 100644 --- a/packages/agentic-fabric/tests/test_yaml_config_errors.py +++ b/packages/agentic-fabric/tests/test_yaml_config_errors.py @@ -112,6 +112,34 @@ def test_missing_tasks_yaml_returns_empty_dict(self, tmp_path: Path) -> None: config = get_crew_config(tmp_path, "test_crew") assert config["tasks"] == {} + def test_agents_and_tasks_yaml_are_read_as_utf8(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Agents and tasks YAML should use explicit UTF-8 decoding.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " test_crew:\n" + " agents: agents.yaml\n" + " tasks: tasks.yaml\n", + encoding="utf-8", + ) + (tmp_path / "agents.yaml").write_text("agent:\n role: Café reviewer\n", encoding="utf-8") + (tmp_path / "tasks.yaml").write_text("task:\n description: Résumé review\n", encoding="utf-8") + original_read_text = Path.read_text + encodings: list[str | None] = [] + + def tracking_read_text(self: Path, *args, **kwargs) -> str: + if self.name in {"agents.yaml", "tasks.yaml"}: + encodings.append(kwargs.get("encoding")) + return original_read_text(self, *args, **kwargs) + + monkeypatch.setattr(Path, "read_text", tracking_read_text) + + config = get_crew_config(tmp_path, "test_crew") + + assert config["agents"]["agent"]["role"] == "Café reviewer" + assert config["tasks"]["task"]["description"] == "Résumé review" + assert encodings == ["utf-8", "utf-8"] + def test_empty_crews_section_raises_for_any_crew(self, tmp_path: Path) -> None: """Empty crews section should raise ValueError.""" manifest_file = tmp_path / "manifest.yaml" From 11b75c22b774c1fdd96bb0e0172145f20f5c0784 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 01:02:38 -0500 Subject: [PATCH 07/35] test: reach full agentic fabric coverage --- .../agentic-fabric/src/agentic_fabric/main.py | 4 +- .../agentic-fabric/tests/test_agentic_data.py | 49 +++ .../agentic-fabric/tests/test_cli_commands.py | 290 ++++++++++++++++++ .../tests/test_decomposer_edge_cases.py | 122 ++++++++ .../agentic-fabric/tests/test_discovery.py | 40 +++ .../tests/test_file_tool_paths.py | 36 +++ .../tests/test_import_surfaces.py | 54 ++++ .../agentic-fabric/tests/test_llm_config.py | 26 ++ packages/agentic-fabric/tests/test_manager.py | 16 + packages/agentic-fabric/tests/test_runners.py | 111 +++++++ .../tests/test_scraping_tool_crawler.py | 16 + .../tests/test_single_agent_runners.py | 56 ++++ .../tests/test_tool_registry.py | 63 ++++ .../agentic-fabric/tests/test_tools_init.py | 16 + .../tests/test_yaml_config_errors.py | 24 ++ 15 files changed, 921 insertions(+), 2 deletions(-) create mode 100644 packages/agentic-fabric/tests/test_import_surfaces.py diff --git a/packages/agentic-fabric/src/agentic_fabric/main.py b/packages/agentic-fabric/src/agentic_fabric/main.py index 440d738..6b0850a 100644 --- a/packages/agentic-fabric/src/agentic_fabric/main.py +++ b/packages/agentic-fabric/src/agentic_fabric/main.py @@ -120,7 +120,7 @@ def cmd_run(args): # Get input if args.file: - input_text = Path(args.file).read_text() + input_text = Path(args.file).read_text(encoding="utf-8") elif args.input: input_text = args.input else: @@ -218,7 +218,7 @@ def _cmd_run_single_agent(args, use_json: bool, start_time: float): # Get input if args.file: - input_text = Path(args.file).read_text() + input_text = Path(args.file).read_text(encoding="utf-8") elif args.input: input_text = args.input else: diff --git a/packages/agentic-fabric/tests/test_agentic_data.py b/packages/agentic-fabric/tests/test_agentic_data.py index 67eb658..0db16b6 100644 --- a/packages/agentic-fabric/tests/test_agentic_data.py +++ b/packages/agentic-fabric/tests/test_agentic_data.py @@ -2,6 +2,11 @@ from __future__ import annotations +import importlib.util +import sys +import types + +from pathlib import Path from typing import Any import pytest @@ -48,6 +53,14 @@ def test_select_runtime_rejects_active_manifest_conflict(monkeypatch: pytest.Mon data.select_runtime(crew_config={"required_framework": "crewai"}) +def test_select_runtime_uses_active_runtime(monkeypatch: pytest.MonkeyPatch) -> None: + """An active runtime should be selected when no explicit runtime is requested.""" + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: runtime == "strands") + data = AgenticData(active_runtime="strands") + + assert data.select_runtime() == "strands" + + def test_run_agent_uses_registered_crew_and_runtime(monkeypatch: pytest.MonkeyPatch) -> None: """Named agents should route through run_crew_auto with merged inputs.""" calls: list[tuple[dict[str, Any], dict[str, Any], str | None]] = [] @@ -98,6 +111,42 @@ def test_fallback_vendor_records_provider_when_not_strict() -> None: data.call("sync") +def test_agentic_data_import_branch_with_vendor_fabric(monkeypatch: pytest.MonkeyPatch) -> None: + """The module should subclass real VendorData when vendor-fabric is importable.""" + import agentic_fabric.agentic_data as current_agentic_data + + fake_vendor_fabric = types.ModuleType("vendor_fabric") + fake_vendor_data = types.ModuleType("vendor_fabric.vendor_data") + + class VendorData: + def __init__(self, value: Any = None, **_: Any) -> None: + self.value = value + + def cast(self, value: Any) -> VendorData: + self.value = value + return self + + fake_vendor_data.VendorData = VendorData + monkeypatch.setitem(sys.modules, "vendor_fabric", fake_vendor_fabric) + monkeypatch.setitem(sys.modules, "vendor_fabric.vendor_data", fake_vendor_data) + + spec = importlib.util.spec_from_file_location( + "_agentic_data_vendor_branch", + Path(current_agentic_data.__file__), + ) + assert spec is not None + assert spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + data = module.AgenticData("value") + + assert module._VENDOR_FABRIC_AVAILABLE is True + assert data.vendor_fabric_available is True + assert isinstance(data, VendorData) + assert data.value == "value" + + def test_agent_registry_is_read_only_and_unregister_is_chainable() -> None: """Agent registry snapshots should be immutable from the public property.""" data = AgenticData(agent_registry={"reviewer": {"name": "review"}}) diff --git a/packages/agentic-fabric/tests/test_cli_commands.py b/packages/agentic-fabric/tests/test_cli_commands.py index 5a8b1fc..fd5923e 100644 --- a/packages/agentic-fabric/tests/test_cli_commands.py +++ b/packages/agentic-fabric/tests/test_cli_commands.py @@ -140,6 +140,17 @@ def test_cmd_run_json_uses_file_input_and_requested_framework( assert run_crew_auto.call_args.kwargs["framework"] == "strands" +def test_cmd_run_dispatches_single_agent_runner(capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(json=False, runner="fake", package=None, crew=None) + calls: list[tuple[SimpleNamespace, bool]] = [] + + with patch.object(cli, "_cmd_run_single_agent", side_effect=lambda args, use_json, start_time: calls.append((args, use_json))): + cli.cmd_run(args) + + assert calls == [(args, False)] + assert capsys.readouterr().out == "" + + def test_cmd_run_json_reports_missing_package(capsys: pytest.CaptureFixture[str]) -> None: args = SimpleNamespace( json=True, @@ -188,6 +199,126 @@ def test_cmd_run_json_reports_execution_errors( assert json.loads(capsys.readouterr().out)["error"] == "bad crew" +def test_cmd_run_text_reports_missing_arguments(capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(json=False, runner=None, package=None, crew=None) + + with pytest.raises(SystemExit) as exc_info: + cli.cmd_run(args) + + assert exc_info.value.code == 2 + assert "Package and crew are required" in capsys.readouterr().out + + +def test_cmd_run_json_reports_missing_arguments(capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(json=True, runner=None, package=None, crew=None) + + with pytest.raises(SystemExit) as exc_info: + cli.cmd_run(args) + + assert exc_info.value.code == 2 + assert json.loads(capsys.readouterr().out)["error"].startswith("Package and crew are required") + + +def test_cmd_run_text_reports_missing_package(capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace( + json=False, + runner=None, + package="missing", + crew="reviewer", + file=None, + input="task", + framework="auto", + ) + + with ( + patch.object(cli, "discover_packages", return_value={"known": Path(".crewai")}), + pytest.raises(SystemExit) as exc_info, + ): + cli.cmd_run(args) + + assert exc_info.value.code == 2 + output = capsys.readouterr().out + assert "Package 'missing' not found" in output + assert "known" in output + + +def test_cmd_run_text_uses_empty_input_and_requested_or_auto_framework( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + args = SimpleNamespace( + json=False, + runner=None, + package="pkg", + crew="reviewer", + file=None, + input=None, + framework="langgraph", + ) + calls: list[dict[str, Any]] = [] + + def fake_run_crew_auto(crew_config: dict[str, Any], inputs: dict[str, str], framework: str | None = None) -> str: + calls.append({"crew_config": crew_config, "inputs": inputs, "framework": framework}) + return "done" + + with ( + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crew"}), + patch.object(cli, "get_crew_config", return_value={"name": "reviewer"}), + patch("agentic_fabric.core.decomposer.run_crew_auto", side_effect=fake_run_crew_auto), + ): + cli.cmd_run(args) + + output = capsys.readouterr().out + assert "Framework: langgraph (requested)" in output + assert calls[0]["inputs"] == {"spec": "", "component_spec": "", "input": ""} + assert calls[0]["framework"] == "langgraph" + + args.framework = "auto" + with ( + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crew"}), + patch.object(cli, "get_crew_config", return_value={"name": "reviewer"}), + patch("agentic_fabric.core.decomposer.detect_framework", return_value="strands"), + patch("agentic_fabric.core.decomposer.run_crew_auto", return_value="done"), + ): + cli.cmd_run(args) + + assert "Framework: strands (auto-detected)" in capsys.readouterr().out + + +def test_cmd_run_text_success_and_error_paths(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace( + json=False, + runner=None, + package="pkg", + crew="reviewer", + file=None, + input="task", + framework="auto", + ) + + with ( + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "get_crew_config", return_value={"name": "reviewer", "required_framework": "crewai"}), + patch("agentic_fabric.core.decomposer.run_crew_auto", return_value="done"), + ): + cli.cmd_run(args) + + output = capsys.readouterr().out + assert "Running pkg/reviewer" in output + assert "Framework: crewai" in output + assert "done" in output + + with ( + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "get_crew_config", side_effect=RuntimeError("boom")), + pytest.raises(SystemExit) as exc_info, + ): + cli.cmd_run(args) + + assert exc_info.value.code == 1 + assert "Error: boom" in capsys.readouterr().out + + def test_cmd_info_json_success(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: args = SimpleNamespace(package="pkg", crew="reviewer", json=True) config = { @@ -225,6 +356,61 @@ def test_cmd_info_json_reports_config_error(tmp_path: Path, capsys: pytest.Captu assert json.loads(capsys.readouterr().out) == {"error": "missing crew"} +def test_cmd_info_json_reports_missing_package(capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(package="missing", crew="reviewer", json=True) + + with ( + patch.object(cli, "discover_packages", return_value={"known": Path(".crewai")}), + pytest.raises(SystemExit) as exc_info, + ): + cli.cmd_info(args) + + assert exc_info.value.code == 2 + data = json.loads(capsys.readouterr().out) + assert data["error"] == "Package 'missing' not found" + assert data["available_packages"] == ["known"] + + +def test_cmd_info_text_success_and_errors(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(package="pkg", crew="reviewer", json=False) + config = { + "description": "Reviews code", + "agents": {"reviewer": {"role": "Code Reviewer"}}, + "tasks": {"review": {"description": "Review the change in detail"}}, + "knowledge_paths": [tmp_path / "knowledge"], + } + + with ( + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "get_crew_config", return_value=config), + ): + cli.cmd_info(args) + + output = capsys.readouterr().out + assert "CREW: pkg/reviewer" in output + assert "Code Reviewer" in output + assert "Review the change" in output + + with ( + patch.object(cli, "discover_packages", return_value={}), + pytest.raises(SystemExit) as exc_info, + ): + cli.cmd_info(args) + + assert exc_info.value.code == 2 + assert "Package 'pkg' not found" in capsys.readouterr().out + + with ( + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "get_crew_config", side_effect=ValueError("missing crew")), + pytest.raises(SystemExit) as exc_info, + ): + cli.cmd_info(args) + + assert exc_info.value.code == 2 + assert "missing crew" in capsys.readouterr().out + + def test_cmd_list_runners_json_skips_bad_profiles(capsys: pytest.CaptureFixture[str]) -> None: fake_runner = FakeCliRunner() @@ -256,6 +442,43 @@ def test_cmd_list_runners_reports_missing_profiles(capsys: pytest.CaptureFixture assert json.loads(capsys.readouterr().out) == {"error": "no profiles"} +def test_cmd_list_runners_text_lists_available_unavailable_and_bad_profiles( + capsys: pytest.CaptureFixture[str], +) -> None: + good_runner = FakeCliRunner(available=True) + bad_runner = FakeCliRunner(available=False) + + def fake_get_cli_runner(profile: str) -> FakeCliRunner: + if profile == "broken": + raise RuntimeError("broken profile") + return good_runner if profile == "good" else bad_runner + + with ( + patch("agentic_fabric.core.decomposer.get_available_cli_runners", return_value=["good", "bad", "broken"]), + patch("agentic_fabric.core.decomposer.get_cli_runner", side_effect=fake_get_cli_runner), + ): + cli.cmd_list_runners(SimpleNamespace(json=False)) + + captured = capsys.readouterr() + assert "AVAILABLE SINGLE-AGENT CLI RUNNERS" in captured.out + assert "good: Runs fake tasks" in captured.out + assert "bad: Runs fake tasks" in captured.out + assert "Install: install fake" in captured.out + assert "Requires: FAKE_API_KEY" in captured.out + assert "Warning: Could not load profile 'broken'" in captured.err + + +def test_cmd_list_runners_text_reports_missing_profiles(capsys: pytest.CaptureFixture[str]) -> None: + with ( + patch("agentic_fabric.core.decomposer.get_available_cli_runners", side_effect=FileNotFoundError("no profiles")), + pytest.raises(SystemExit) as exc_info, + ): + cli.cmd_list_runners(SimpleNamespace(json=False)) + + assert exc_info.value.code == 2 + assert "Error: no profiles" in capsys.readouterr().out + + def test_single_agent_json_success_uses_package_workdir( tmp_path: Path, capsys: pytest.CaptureFixture[str], @@ -301,6 +524,16 @@ def test_single_agent_json_requires_input(capsys: pytest.CaptureFixture[str]) -> assert json.loads(capsys.readouterr().out)["error"] == "No input provided. Use --input or --file" +def test_single_agent_text_requires_input(capsys: pytest.CaptureFixture[str]) -> None: + args = SimpleNamespace(json=False, runner="fake", input=None, file=None, package=None) + + with pytest.raises(SystemExit) as exc_info: + cli._cmd_run_single_agent(args, use_json=False, start_time=0) + + assert exc_info.value.code == 2 + assert "No input provided" in capsys.readouterr().out + + def test_single_agent_json_reports_unavailable_runner(capsys: pytest.CaptureFixture[str]) -> None: args = SimpleNamespace(json=True, runner="fake", input="task", file=None, package=None, model=None) fake_runner = FakeCliRunner(available=False) @@ -318,6 +551,63 @@ def test_single_agent_json_reports_unavailable_runner(capsys: pytest.CaptureFixt assert data["available_runners"] == ["other"] +def test_single_agent_text_success_unavailable_and_error_paths( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + input_file = tmp_path / "task.txt" + input_file.write_text("from file", encoding="utf-8") + package_dir = tmp_path / "pkg" / ".crew" + fake_runner = FakeCliRunner(result="fixed") + args = SimpleNamespace( + json=False, + runner="fake", + input=None, + file=str(input_file), + package="pkg", + model=None, + auto_approve=True, + ) + + with ( + patch.object(cli, "discover_packages", return_value={"pkg": package_dir}), + patch("agentic_fabric.core.decomposer.get_cli_runner", return_value=fake_runner), + ): + cli._cmd_run_single_agent(args, use_json=False, start_time=0) + + output = capsys.readouterr().out + assert "Running single-agent: fake" in output + assert "Runner: Fake Runner" in output + assert "fixed" in output + assert fake_runner.calls[0]["task"] == "from file" + + unavailable_runner = FakeCliRunner(available=False) + args.input = "task" + args.file = None + with ( + patch("agentic_fabric.core.decomposer.get_cli_runner", return_value=unavailable_runner), + patch("agentic_fabric.core.decomposer.get_available_cli_runners", return_value=["other"]), + pytest.raises(SystemExit) as exc_info, + ): + cli._cmd_run_single_agent(args, use_json=False, start_time=0) + + assert exc_info.value.code == 2 + assert "Runner 'fake' not available" in capsys.readouterr().out + + def broken_run(**kwargs: Any) -> str: + raise FileNotFoundError("missing binary") + + fake_runner.run = broken_run + with ( + patch("agentic_fabric.core.decomposer.get_cli_runner", return_value=fake_runner), + pytest.raises(SystemExit) as exc_info, + ): + cli._cmd_run_single_agent(args, use_json=False, start_time=0) + + assert exc_info.value.code == 1 + assert "missing binary" in capsys.readouterr().out + + def test_single_agent_json_reports_runner_errors(capsys: pytest.CaptureFixture[str]) -> None: args = SimpleNamespace(json=True, runner="fake", input="task", file=None, package=None, model=None) fake_runner = FakeCliRunner() diff --git a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py index 4e49b92..31eaf18 100644 --- a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py +++ b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py @@ -9,10 +9,14 @@ from agentic_fabric.core.decomposer import ( _framework_cache, _get_install_command, + decompose_crew, detect_framework, get_available_frameworks, get_framework_info, + get_runner, + is_cli_runner_available, is_framework_available, + run_crew_auto, ) from agentic_fabric.runners.registry import clear_runtime_cache @@ -160,6 +164,124 @@ def mock_available(framework): assert result == ["crewai", "strands"] +class TestRunnerDispatch: + """Tests for framework runner dispatch helpers.""" + + def test_get_runner_auto_detects_and_instantiates_runner(self, monkeypatch: pytest.MonkeyPatch) -> None: + """get_runner should instantiate the registered runner class.""" + + class FakeRunner: + pass + + class FakeSpec: + runner_module = "fake.runner" + runner_class = "FakeRunner" + + fake_module = type("FakeModule", (), {"FakeRunner": FakeRunner}) + monkeypatch.setattr("agentic_fabric.core.decomposer.detect_framework", lambda: "fake") + monkeypatch.setattr("agentic_fabric.core.decomposer.get_runtime_spec", lambda framework: FakeSpec()) + monkeypatch.setattr("agentic_fabric.core.decomposer.importlib.import_module", lambda module_name: fake_module) + + assert isinstance(get_runner(), FakeRunner) + + def test_get_runner_wraps_unknown_framework(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Unknown framework names should include registered options.""" + + def fake_get_runtime_spec(framework: str): + raise ValueError("unknown") + + monkeypatch.setattr("agentic_fabric.core.decomposer.get_runtime_spec", fake_get_runtime_spec) + + with pytest.raises(ValueError, match="Unknown framework: unknown"): + get_runner("unknown") + + def test_is_cli_runner_available_handles_available_and_missing_profiles( + self, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """CLI availability should map runner errors to False.""" + + class FakeCliRunner: + def is_available(self) -> bool: + return True + + monkeypatch.setattr("agentic_fabric.core.decomposer.get_cli_runner", lambda profile: FakeCliRunner()) + assert is_cli_runner_available("ok") is True + + monkeypatch.setattr( + "agentic_fabric.core.decomposer.get_cli_runner", + lambda profile: (_ for _ in ()).throw(ValueError("missing")), + ) + assert is_cli_runner_available("missing") is False + + def test_decompose_crew_enforces_required_runtime_and_builds( + self, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """Required framework configs should build through that runtime.""" + + class FakeRunner: + def build_crew(self, crew_config): + return {"built": crew_config} + + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda framework: True) + monkeypatch.setattr("agentic_fabric.core.decomposer.get_runner", lambda framework: FakeRunner()) + + crew_config = {"required_framework": "crewai", "name": "reviewer"} + + assert decompose_crew(crew_config, framework="auto") == {"built": crew_config} + + def test_decompose_crew_reports_conflicting_or_missing_required_runtime( + self, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """Required runtime errors should surface clear guidance.""" + crew_config = {"required_framework": "crewai"} + + with pytest.raises(ValueError, match="Crew requires crewai"): + decompose_crew(crew_config, framework="strands") + + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda framework: False) + + with pytest.raises(RuntimeError, match="pip install crewai\\[tools\\]"): + decompose_crew(crew_config) + + def test_run_crew_auto_enforces_required_runtime_and_runs( + self, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """run_crew_auto should build and run through the selected runner.""" + + class FakeRunner: + def build_crew(self, crew_config): + return {"crew": crew_config} + + def run(self, crew, inputs): + return f"{crew['crew']['name']}:{inputs['task']}" + + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda framework: True) + monkeypatch.setattr("agentic_fabric.core.decomposer.get_runner", lambda framework: FakeRunner()) + + result = run_crew_auto({"name": "reviewer", "required_framework": "langgraph"}, {"task": "go"}) + + assert result == "reviewer:go" + + def test_run_crew_auto_reports_conflicting_or_missing_required_runtime( + self, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """run_crew_auto should reject conflicts and unavailable required runtimes.""" + crew_config = {"required_framework": "langgraph"} + + with pytest.raises(ValueError, match="Crew requires langgraph"): + run_crew_auto(crew_config, framework="crewai") + + monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda framework: False) + + with pytest.raises(RuntimeError, match="pip install langgraph"): + run_crew_auto(crew_config) + + class TestFrameworkInfo: """Tests for lazy runtime registry metadata.""" diff --git a/packages/agentic-fabric/tests/test_discovery.py b/packages/agentic-fabric/tests/test_discovery.py index 6795d8f..6dd7328 100644 --- a/packages/agentic-fabric/tests/test_discovery.py +++ b/packages/agentic-fabric/tests/test_discovery.py @@ -82,6 +82,34 @@ def test_discover_all_framework_configs(self, tmp_path: Path) -> None: assert None in configs["multi"] # .crew -> None (agnostic) assert "crewai" in configs["multi"] + def test_discover_all_framework_configs_uses_default_root_and_skips_files( + self, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """All-framework discovery should use the default root and skip non-package files.""" + from agentic_fabric.core.discovery import discover_all_framework_configs + + packages_dir = tmp_path / "packages" + packages_dir.mkdir() + (packages_dir / "README.md").write_text("not a package", encoding="utf-8") + + pkg_dir = packages_dir / "multi" + (pkg_dir / ".crew").mkdir(parents=True) + (pkg_dir / ".crew" / "manifest.yaml").write_text("name: multi\ncrews: {}\n", encoding="utf-8") + (pkg_dir / ".strands").mkdir() + (pkg_dir / ".strands" / "manifest.yaml").write_text("name: multi\ncrews: {}\n", encoding="utf-8") + + (tmp_path / ".langgraph").mkdir() + (tmp_path / ".langgraph" / "manifest.yaml").write_text("name: root\ncrews: {}\n", encoding="utf-8") + monkeypatch.setattr("agentic_fabric.core.discovery.get_workspace_root", lambda: tmp_path) + + configs = discover_all_framework_configs() + + assert configs["multi"][None] == pkg_dir / ".crew" + assert configs["multi"]["strands"] == pkg_dir / ".strands" + assert configs[tmp_path.name]["langgraph"] == tmp_path / ".langgraph" + def test_list_crews_returns_crews_from_manifest(self, temp_workspace: Path) -> None: """Test that list_crews returns crew definitions from manifest.""" from agentic_fabric.core.discovery import list_crews @@ -143,6 +171,18 @@ def test_get_workspace_root_finds_root(self) -> None: # Verify it looks like a workspace root assert (root / "packages").exists() or root == Path.cwd() + def test_get_workspace_root_falls_back_to_cwd(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Workspace root discovery should fall back when no markers are found.""" + from agentic_fabric.core import discovery + + module_path = tmp_path / "installed" / "module.py" + module_path.parent.mkdir() + module_path.write_text("# module\n", encoding="utf-8") + monkeypatch.setattr(discovery, "__file__", str(module_path)) + monkeypatch.chdir(tmp_path) + + assert discovery.get_workspace_root() == tmp_path + def test_get_framework_from_config_dir(self) -> None: """Test framework detection from directory name.""" from agentic_fabric.core.discovery import get_framework_from_config_dir diff --git a/packages/agentic-fabric/tests/test_file_tool_paths.py b/packages/agentic-fabric/tests/test_file_tool_paths.py index 3f9c1e7..2a89cb4 100644 --- a/packages/agentic-fabric/tests/test_file_tool_paths.py +++ b/packages/agentic-fabric/tests/test_file_tool_paths.py @@ -76,6 +76,28 @@ def test_workspace_root_resolution_uses_workspace_env_and_cwd( assert file_tools.get_workspace_root("otterfall") == tmp_path +def test_find_workspace_root_finds_repository_root(monkeypatch: pytest.MonkeyPatch) -> None: + """The marker-file search should find the checked-out workspace root.""" + file_tools = import_file_tools_with_fake_crewai(monkeypatch) + + root = file_tools._find_workspace_root() + + assert root is not None + assert (root / "pyproject.toml").exists() + assert (root / "packages").is_dir() + + +def test_find_workspace_root_returns_none_without_markers(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """The marker-file search should return None for installed modules outside a workspace.""" + file_tools = import_file_tools_with_fake_crewai(monkeypatch) + module_path = tmp_path / "installed" / "file_tools.py" + module_path.parent.mkdir() + module_path.write_text("# module\n", encoding="utf-8") + monkeypatch.setattr(file_tools, "__file__", str(module_path)) + + assert file_tools._find_workspace_root() is None + + def test_clean_relative_path_rejects_invalid_values(monkeypatch: pytest.MonkeyPatch) -> None: """Relative path cleaning should reject empty, absolute, and traversal paths.""" file_tools = import_file_tools_with_fake_crewai(monkeypatch) @@ -166,3 +188,17 @@ def raise_unexpected(path_value: str) -> tuple[str, Path]: assert "Error writing file: boom" in file_tools.GameCodeWriterTool()._run("src/ecs/data/file.ts", "") assert "Error reading file: boom" in file_tools.GameCodeReaderTool()._run("src/ecs/data/file.ts") assert "Error listing directory: boom" in file_tools.DirectoryListTool()._run("src/ecs/data") + + +def test_file_tools_convert_path_validation_errors(monkeypatch: pytest.MonkeyPatch) -> None: + """ValueError path validation failures should be returned as agent-readable errors.""" + file_tools = import_file_tools_with_fake_crewai(monkeypatch) + + def raise_value_error(path_value: str) -> tuple[str, Path]: + raise ValueError("bad path") + + monkeypatch.setattr(file_tools, "_resolve_workspace_path", raise_value_error) + + assert file_tools.GameCodeWriterTool()._run("../file.ts", "") == "Error: bad path" + assert file_tools.GameCodeReaderTool()._run("../file.ts") == "Error: bad path" + assert file_tools.DirectoryListTool()._run("../") == "Error: bad path" diff --git a/packages/agentic-fabric/tests/test_import_surfaces.py b/packages/agentic-fabric/tests/test_import_surfaces.py new file mode 100644 index 0000000..905d8bf --- /dev/null +++ b/packages/agentic-fabric/tests/test_import_surfaces.py @@ -0,0 +1,54 @@ +"""Tests for small import and module-entry surfaces.""" + +from __future__ import annotations + +import importlib +import runpy +import sys +import types + +import pytest + + +def test_base_exports_file_tools_with_fake_crewai(monkeypatch: pytest.MonkeyPatch) -> None: + """The base package should re-export file tools when CrewAI is present.""" + fake_crewai = types.ModuleType("crewai") + fake_tools = types.ModuleType("crewai.tools") + + class BaseTool: + """Minimal BaseTool stand-in.""" + + fake_tools.BaseTool = BaseTool + monkeypatch.setitem(sys.modules, "crewai", fake_crewai) + monkeypatch.setitem(sys.modules, "crewai.tools", fake_tools) + sys.modules.pop("agentic_fabric.tools.file_tools", None) + sys.modules.pop("agentic_fabric.base", None) + + base = importlib.import_module("agentic_fabric.base") + + assert base.__all__ == ["DirectoryListTool", "GameCodeReaderTool", "GameCodeWriterTool"] + assert base.DirectoryListTool.__name__ == "DirectoryListTool" + + +def test_module_entrypoint_delegates_to_main(monkeypatch: pytest.MonkeyPatch) -> None: + """python -m agentic_fabric should call the CLI main function.""" + from agentic_fabric import main as cli_main + + calls: list[str] = [] + monkeypatch.setattr(cli_main, "main", lambda: calls.append("main")) + sys.modules.pop("agentic_fabric.__main__", None) + + runpy.run_module("agentic_fabric.__main__", run_name="__main__") + + assert calls == ["main"] + + +def test_cli_script_entrypoint_prints_help(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: + """Executing main.py as a script should use the same CLI entrypoint.""" + from agentic_fabric import main as cli_main + + monkeypatch.setattr(sys, "argv", ["agentic-fabric"]) + + runpy.run_path(str(cli_main.__file__), run_name="__main__") + + assert "agentic-fabric - Framework-Agnostic Crew Runner" in capsys.readouterr().out diff --git a/packages/agentic-fabric/tests/test_llm_config.py b/packages/agentic-fabric/tests/test_llm_config.py index 5955b5a..eebceed 100644 --- a/packages/agentic-fabric/tests/test_llm_config.py +++ b/packages/agentic-fabric/tests/test_llm_config.py @@ -77,6 +77,21 @@ def test_get_llm_can_force_openrouter_and_normalizes_model(monkeypatch: pytest.M } +def test_get_llm_can_force_anthropic(monkeypatch: pytest.MonkeyPatch) -> None: + clear_llm_env(monkeypatch) + enable_fake_llm(monkeypatch) + monkeypatch.setenv("ANTHROPIC_API_KEY", "anthropic-key") + + result = llm_config.get_llm("claude-haiku-4-5-20251001", temperature=0.1, provider=LLMProvider.ANTHROPIC) + + assert isinstance(result, FakeLLM) + assert result.kwargs == { + "model": "claude-haiku-4-5-20251001", + "api_key": "anthropic-key", + "temperature": 0.1, + } + + def test_get_llm_preserves_openrouter_model_in_auto_detection(monkeypatch: pytest.MonkeyPatch) -> None: enable_fake_llm(monkeypatch) monkeypatch.setenv("ANTHROPIC_API_KEY", "anthropic-key") @@ -111,6 +126,17 @@ def test_get_llm_or_raise_reports_install_guidance(monkeypatch: pytest.MonkeyPat llm_config.get_llm_or_raise() +def test_get_llm_or_raise_returns_configured_llm(monkeypatch: pytest.MonkeyPatch) -> None: + clear_llm_env(monkeypatch) + enable_fake_llm(monkeypatch) + monkeypatch.setenv("OPENROUTER_API_KEY", "openrouter-key") + + result = llm_config.get_llm_or_raise(provider=LLMProvider.OPENROUTER) + + assert isinstance(result, FakeLLM) + assert result.kwargs["api_key"] == "openrouter-key" + + def test_task_helpers_use_declared_configs(monkeypatch: pytest.MonkeyPatch) -> None: calls: list[tuple[str, float]] = [] diff --git a/packages/agentic-fabric/tests/test_manager.py b/packages/agentic-fabric/tests/test_manager.py index 6826a72..56e4569 100644 --- a/packages/agentic-fabric/tests/test_manager.py +++ b/packages/agentic-fabric/tests/test_manager.py @@ -159,6 +159,22 @@ def get_config_side_effect(pkg_dir, crew_name): # Should have tried pkg1 (failed), then found in pkg2 (config is cached) assert mock_get_config.call_count == 2 + def test_delegate_uses_cached_crew_config(self): + """Repeated delegation to the same crew should reuse the cached config.""" + manager = ManagerAgent(crews={"design": "game_design"}, package_name="test_pkg") + mock_config = {"name": "game_design", "agents": {}, "tasks": {}} + + with ( + patch("agentic_fabric.core.manager.discover_packages", return_value={"test_pkg": Path("/test/.crewai")}), + patch("agentic_fabric.core.manager.get_crew_config", return_value=mock_config) as mock_get_config, + patch("agentic_fabric.core.manager.run_crew_auto", side_effect=["first", "second"]) as mock_run, + ): + assert manager.delegate("design", "first task", framework="crewai") == "first" + assert manager.delegate("design", {"task": "second task"}, framework="langgraph") == "second" + + mock_get_config.assert_called_once_with(Path("/test/.crewai"), "game_design") + assert mock_run.call_args_list[1].kwargs == {"inputs": {"task": "second task"}, "framework": "langgraph"} + def test_delegate_crew_not_found_raises_error(self): """Test that crew not found in any package raises ValueError.""" manager = ManagerAgent(crews={"design": "nonexistent_crew"}) diff --git a/packages/agentic-fabric/tests/test_runners.py b/packages/agentic-fabric/tests/test_runners.py index b4bc5a1..52ed188 100644 --- a/packages/agentic-fabric/tests/test_runners.py +++ b/packages/agentic-fabric/tests/test_runners.py @@ -6,6 +6,7 @@ from __future__ import annotations +import builtins import sys from typing import TYPE_CHECKING, Any @@ -15,9 +16,34 @@ from tests._crew_mocker import CrewMocker +def reject_imports(*blocked_names: str): + """Return an import hook that raises for selected top-level packages.""" + real_import = builtins.__import__ + + def fake_import(name: str, *args: Any, **kwargs: Any) -> Any: + if name in blocked_names: + raise ImportError(name) + return real_import(name, *args, **kwargs) + + return fake_import + + class TestCrewAIRunner: """Tests for CrewAI runner implementation.""" + def test_init_reports_missing_crewai(self, crew_mocker: CrewMocker) -> None: + """CrewAI runner construction should explain how to install CrewAI.""" + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + crew_mocker.patch("builtins.__import__", side_effect=reject_imports("crewai")) + + try: + CrewAIRunner() + except RuntimeError as exc: + assert "pip install crewai[tools]" in str(exc) + else: # pragma: no cover - defensive if CrewAI import mocking stops working + raise AssertionError("CrewAIRunner did not report missing CrewAI") + def test_build_agent_creates_crewai_agent(self, crew_mocker: CrewMocker) -> None: """Test that build_agent creates a CrewAI Agent.""" crew_mocker.mock_crewai() @@ -286,10 +312,70 @@ def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> No runner._resolve_tools.assert_called_once_with(["FileWriteTool"]) assert MockAgent.call_args[1]["tools"] == [resolved_tool] + def test_build_crew_rejects_tasks_with_unknown_agents(self, crew_mocker: CrewMocker) -> None: + """CrewAI tasks must reference agents declared in the crew config.""" + crew_mocker.mock_crewai() + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + runner = CrewAIRunner() + + crew_config = { + "agents": {}, + "tasks": {"task1": {"description": "Task", "agent": "missing"}}, + } + + try: + runner.build_crew(crew_config) + except ValueError as exc: + assert "invalid agent: missing" in str(exc) + else: # pragma: no cover - defensive + raise AssertionError("CrewAIRunner accepted a task with an unknown agent") + + def test_load_knowledge_skips_non_directories_and_logs_read_errors( + self, + crew_mocker: CrewMocker, + tmp_path, + ) -> None: + """Knowledge loading should skip bad paths and tolerate unreadable files.""" + crew_mocker.mock_crewai() + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + crew_mocker.patch_knowledge_source() + runner = CrewAIRunner() + knowledge_dir = tmp_path / "knowledge" + knowledge_dir.mkdir() + bad_file = knowledge_dir / "bad.md" + bad_file.write_text("content", encoding="utf-8") + original_read_text = type(bad_file).read_text + + def fake_read_text(self, *args: Any, **kwargs: Any) -> str: + if self == bad_file: + raise OSError("cannot read") + return original_read_text(self, *args, **kwargs) + + crew_mocker.patch.object(type(bad_file), "read_text", fake_read_text) + + assert runner._load_knowledge([tmp_path / "missing", knowledge_dir]) == [] + class TestLangGraphRunner: """Tests for LangGraph runner implementation.""" + def test_init_reports_missing_langgraph(self, crew_mocker: CrewMocker) -> None: + """LangGraph runner construction should explain how to install LangGraph.""" + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + crew_mocker.patch("builtins.__import__", side_effect=reject_imports("langgraph")) + + try: + LangGraphRunner() + except RuntimeError as exc: + assert "pip install langgraph" in str(exc) + else: # pragma: no cover - defensive if LangGraph import mocking stops working + raise AssertionError("LangGraphRunner did not report missing LangGraph") + def test_build_agent_creates_react_agent(self, crew_mocker: CrewMocker) -> None: """Test that build_agent creates a LangGraph ReAct agent.""" crew_mocker.mock_langgraph() @@ -373,6 +459,18 @@ def test_run_handles_dict_messages(self, crew_mocker: CrewMocker) -> None: assert "messages" in invoke_args assert any("test task" in str(msg) for msg in invoke_args["messages"]) + def test_run_returns_raw_result_when_no_messages(self, crew_mocker: CrewMocker) -> None: + """LangGraph runner should stringify results that do not contain messages.""" + crew_mocker.mock_langgraph() + + from agentic_fabric.runners.langgraph_runner import LangGraphRunner + + runner = LangGraphRunner() + mock_graph = crew_mocker.MagicMock() + mock_graph.invoke.return_value = {"state": "done"} + + assert runner.run(mock_graph, {"other": "value"}) == "{'state': 'done'}" + def test_get_llm_returns_chat_anthropic(self, crew_mocker: CrewMocker) -> None: """Test that get_llm returns ChatAnthropic instance.""" crew_mocker.mock_langgraph() @@ -473,6 +571,19 @@ def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> No class TestStrandsRunner: """Tests for Strands runner implementation.""" + def test_init_reports_missing_strands(self, crew_mocker: CrewMocker) -> None: + """Strands runner construction should explain how to install Strands.""" + from agentic_fabric.runners.strands_runner import StrandsRunner + + crew_mocker.patch("builtins.__import__", side_effect=reject_imports("strands")) + + try: + StrandsRunner() + except RuntimeError as exc: + assert "pip install strands-agents" in str(exc) + else: # pragma: no cover - defensive if Strands import mocking stops working + raise AssertionError("StrandsRunner did not report missing Strands") + def test_build_agent_creates_strands_agent(self, crew_mocker: CrewMocker) -> None: """Test that build_agent creates a Strands Agent.""" crew_mocker.mock_strands() diff --git a/packages/agentic-fabric/tests/test_scraping_tool_crawler.py b/packages/agentic-fabric/tests/test_scraping_tool_crawler.py index 1037696..ab929b9 100644 --- a/packages/agentic-fabric/tests/test_scraping_tool_crawler.py +++ b/packages/agentic-fabric/tests/test_scraping_tool_crawler.py @@ -72,3 +72,19 @@ def test_crawler_marks_failed_url_visited_before_retry(monkeypatch: pytest.Monke assert "duplicate-fail" in result assert scraping_tools.requests.get.call_count == 2 + + +def test_scrape_content_removes_script_and_style_tags(monkeypatch: pytest.MonkeyPatch) -> None: + """The scraper should remove script/style nodes before reading text.""" + scraping_tools = import_scraping_tools(monkeypatch) + script = MagicMock() + style = MagicMock() + soup = MagicMock() + soup.return_value = [script, style] + soup.stripped_strings = ["Visible", "content"] + + result = scraping_tools.CrawlWebsiteTool()._scrape_content(soup) + + script.decompose.assert_called_once_with() + style.decompose.assert_called_once_with() + assert result == "Visible content" diff --git a/packages/agentic-fabric/tests/test_single_agent_runners.py b/packages/agentic-fabric/tests/test_single_agent_runners.py index 3f30ca2..a296073 100644 --- a/packages/agentic-fabric/tests/test_single_agent_runners.py +++ b/packages/agentic-fabric/tests/test_single_agent_runners.py @@ -149,6 +149,13 @@ def test_rejects_group_writable_profiles_file(self, mock_profiles_file: Path): with pytest.raises(PermissionError, match="writable by group or other"): LocalCLIRunner._load_profiles() + def test_profiles_permission_check_is_noop_on_non_posix(self, mock_profiles_file: Path): + """Non-POSIX platforms should skip POSIX mode checks.""" + from agentic_fabric.runners import local_cli_runner + + with patch.object(local_cli_runner.os, "name", "nt"): + local_cli_runner._validate_profiles_file_permissions(mock_profiles_file) + def test_init_with_profile_name(self, mock_profiles_file: Path): """Should initialize with a profile name.""" with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: @@ -192,6 +199,18 @@ def test_rejects_unknown_config_dict_fields(self): with pytest.raises(ValueError, match="unsupported fields"): LocalCLIRunner({"command": "my-tool", "task_flag": "--prompt", "shell": True}) + @pytest.mark.parametrize( + "config,error_type,match", + [ + ({"command": "", "task_flag": "--prompt"}, ValueError, "non-empty command"), + ({"command": "my-tool", "task_flag": None}, TypeError, "task_flag as a string"), + ], + ) + def test_rejects_invalid_required_scalar_fields(self, config, error_type, match): + """Required scalar profile fields should have strict types.""" + with pytest.raises(error_type, match=match): + LocalCLIRunner(config) + def test_rejects_shell_operator_in_command(self): """Profile commands should be direct executable invocations.""" with pytest.raises(ValueError, match="direct executable"): @@ -202,11 +221,28 @@ def test_rejects_invalid_additional_flags(self): with pytest.raises(ValueError, match="additional_flags"): LocalCLIRunner({"command": "my-tool", "task_flag": "--prompt", "additional_flags": ["--ok", ""]}) + def test_rejects_invalid_optional_scalar_field(self): + """Optional string fields should reject non-string values.""" + with pytest.raises(ValueError, match="working_dir_flag"): + LocalCLIRunner({"command": "my-tool", "task_flag": "--prompt", "working_dir_flag": 123}) + def test_rejects_invalid_timeout(self): """Timeouts should stay within the bounded execution range.""" with pytest.raises(ValueError, match="timeout"): LocalCLIRunner({"command": "my-tool", "task_flag": "--prompt", "timeout": 0}) + def test_load_profiles_reports_missing_profiles_file(self, tmp_path: Path): + """Missing bundled profiles should raise clear guidance.""" + missing_profiles_file = tmp_path / "local_cli_profiles.yaml" + with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: + mock_path.return_value.parent = tmp_path + mock_path.return_value.__truediv__.return_value = missing_profiles_file + + LocalCLIRunner._profiles_cache = None + + with pytest.raises(FileNotFoundError, match=r"Expected local_cli_profiles\.yaml"): + LocalCLIRunner._load_profiles() + def test_init_with_config_object(self): """Should initialize with a LocalCLIConfig object.""" config = LocalCLIConfig( @@ -448,3 +484,23 @@ def test_build_command_with_subcommand(self, mock_run: MagicMock): assert cmd[0] == "ollama" assert cmd[1] == "run" assert "codellama" in cmd + + @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) + @patch("subprocess.run") + def test_build_command_with_positional_model_and_working_dir_flag(self, mock_run: MagicMock): + """Positional models and supported working-dir flags should be included.""" + config = LocalCLIConfig( + command="ollama", + subcommand="run", + task_flag="", + auth_env=["TEST_API_KEY"], + working_dir_flag="--cwd", + ) + runner = LocalCLIRunner(config, model="deepseek-coder") + + mock_run.return_value = MagicMock(returncode=0, stdout="Output", stderr="") + + runner.run("Test task", working_dir="/tmp/work") + + cmd = mock_run.call_args[0][0] + assert cmd == ["ollama", "run", "deepseek-coder", "Test task", "--cwd", "/tmp/work"] diff --git a/packages/agentic-fabric/tests/test_tool_registry.py b/packages/agentic-fabric/tests/test_tool_registry.py index 1e8296e..77d0711 100644 --- a/packages/agentic-fabric/tests/test_tool_registry.py +++ b/packages/agentic-fabric/tests/test_tool_registry.py @@ -6,6 +6,8 @@ from unittest.mock import MagicMock, patch +import pytest + from agentic_fabric.tools.registry import register_tool_factory, resolve_tool, resolve_tools from agentic_fabric.tools.vendor import VendorCapabilityTool @@ -56,6 +58,36 @@ def test_skips_unallowed_fully_qualified_reference(self, mock_import_module: Mag assert result is None mock_import_module.assert_not_called() + @patch("agentic_fabric.tools.registry.importlib.import_module") + def test_resolves_allowed_dotted_reference(self, mock_import_module: MagicMock) -> None: + """Allowed dotted references should resolve module and attribute names.""" + tool = object() + mock_tool_class = MagicMock(return_value=tool) + mock_module = MagicMock() + mock_module.CustomTool = mock_tool_class + mock_import_module.return_value = mock_module + + result = resolve_tool("agentic_fabric.custom.CustomTool") + + mock_import_module.assert_called_once_with("agentic_fabric.custom") + assert result is tool + + @patch("agentic_fabric.tools.registry.importlib.import_module") + def test_skips_unallowed_dotted_reference(self, mock_import_module: MagicMock) -> None: + """Unallowlisted dotted references should not be imported.""" + result = resolve_tool("external_package.tools.CustomTool") + + assert result is None + mock_import_module.assert_not_called() + + @pytest.mark.parametrize("side_effect", [ImportError("missing"), AttributeError("missing attr")]) + @patch("agentic_fabric.tools.registry.importlib.import_module") + def test_returns_none_when_dynamic_import_fails(self, mock_import_module: MagicMock, side_effect: Exception) -> None: + """Failed dynamic imports should be skipped cleanly.""" + mock_import_module.side_effect = side_effect + + assert resolve_tool("agentic_fabric.missing:Tool") is None + def test_resolves_registered_factory_alias(self) -> None: """Application code can register safe tool factories directly.""" tool = object() @@ -86,6 +118,15 @@ def test_resolves_vendor_colon_tool(self) -> None: assert tool.provider == "slack" assert tool.operation == "list_messages" + @pytest.mark.parametrize("tool_name", ["vendor://github", "vendor:github"]) + def test_skips_malformed_vendor_tool_references(self, tool_name: str) -> None: + """Malformed vendor references should behave like unresolved tools.""" + assert resolve_tool(tool_name) is None + + def test_skips_malformed_dotted_reference(self) -> None: + """Malformed dotted references should resolve to None without importing.""" + assert resolve_tool(".Tool") is None + @patch("agentic_fabric.tools.registry.importlib.import_module") def test_resolve_tools_deduplicates_aliases(self, mock_import_module: MagicMock) -> None: """Aliases pointing to the same tool should not instantiate duplicates.""" @@ -101,6 +142,14 @@ def test_resolve_tools_deduplicates_aliases(self, mock_import_module: MagicMock) assert len(tools) == 1 mock_tool_class.assert_called_once_with() + def test_resolve_tools_skips_unresolved_unique_names(self) -> None: + """Unresolved tools should not block later unique tools.""" + tool = object() + factory = MagicMock(return_value=tool) + register_tool_factory("UnitTestResolvedTool", factory) + + assert resolve_tools(["mcp://missing/tool", "UnitTestResolvedTool"]) == [tool] + class TestVendorCapabilityTool: """Tests for the generic vendor-backed tool wrapper.""" @@ -115,3 +164,17 @@ def test_runs_operation_through_supplied_data(self) -> None: assert result == {"ok": True} data.call.assert_called_once_with("get_file", "github", path="README.md") + + def test_runs_operation_through_default_agentic_data(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Without supplied data, the wrapper should create AgenticData lazily.""" + fake_data = MagicMock() + + class FakeAgenticData: + def __new__(cls) -> MagicMock: + return fake_data + + monkeypatch.setattr("agentic_fabric.agentic_data.AgenticData", FakeAgenticData) + + VendorCapabilityTool("github", "get_file")(path="README.md") + + fake_data.call.assert_called_once_with("get_file", "github", path="README.md") diff --git a/packages/agentic-fabric/tests/test_tools_init.py b/packages/agentic-fabric/tests/test_tools_init.py index 6a5aab4..ec3371b 100644 --- a/packages/agentic-fabric/tests/test_tools_init.py +++ b/packages/agentic-fabric/tests/test_tools_init.py @@ -32,6 +32,22 @@ def fake_load_attr(module_name: str, attr_name: str) -> type[FakeTool]: ] +def test_load_attr_imports_module_attribute(monkeypatch: pytest.MonkeyPatch) -> None: + imported: list[str] = [] + + class FakeModule: + Tool = FakeTool + + def fake_import_module(module_name: str) -> type[FakeModule]: + imported.append(module_name) + return FakeModule + + monkeypatch.setattr(tools, "import_module", fake_import_module) + + assert tools._load_attr("example.tools", "Tool") is FakeTool + assert imported == ["example.tools"] + + def test_getattr_rejects_unknown_name() -> None: with pytest.raises(AttributeError, match="does-not-exist"): tools.__getattr__("does-not-exist") diff --git a/packages/agentic-fabric/tests/test_yaml_config_errors.py b/packages/agentic-fabric/tests/test_yaml_config_errors.py index 7c6db35..49364f3 100644 --- a/packages/agentic-fabric/tests/test_yaml_config_errors.py +++ b/packages/agentic-fabric/tests/test_yaml_config_errors.py @@ -206,6 +206,30 @@ def test_agents_path_cannot_escape_config_dir(self, tmp_path: Path) -> None: with pytest.raises(ValueError, match="Manifest path must be relative"): get_crew_config(tmp_path, "test_crew") + def test_agents_symlink_path_cannot_escape_config_dir(self, tmp_path: Path) -> None: + """Resolved symlinks must not escape the config directory.""" + config_dir = tmp_path / ".crew" + config_dir.mkdir() + outside = tmp_path / "outside_agents.yaml" + outside.write_text("agent: {}\n", encoding="utf-8") + link_path = config_dir / "agents-link.yaml" + try: + link_path.symlink_to(outside) + except (OSError, NotImplementedError) as exc: + pytest.skip(f"symlinks unavailable: {exc}") + + manifest_file = config_dir / "manifest.yaml" + manifest_file.write_text( + "crews:\n" + " test_crew:\n" + " agents: agents-link.yaml\n" + " tasks: tasks.yaml\n", + encoding="utf-8", + ) + + with pytest.raises(ValueError, match="Manifest path escapes config directory"): + get_crew_config(config_dir, "test_crew") + def test_knowledge_path_cannot_escape_config_dir(self, tmp_path: Path) -> None: """Manifest knowledge paths must stay inside the config directory.""" manifest_file = tmp_path / "manifest.yaml" From df6d4ae6ce4bb729e26bc39b9c24a869d54ad8ad Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 01:19:17 -0500 Subject: [PATCH 08/35] docs: align runtime extra guidance --- docs/agentic-workflows.rst | 24 +++++++++++ docs/getting-started.rst | 33 ++++++++++++++ docs/index.rst | 1 + packages/agentic-fabric/README.md | 43 ++++++++++++++++++- .../src/agentic_fabric/core/decomposer.py | 24 ++++------- .../agentic_fabric/runners/crewai_runner.py | 3 +- .../runners/langgraph_runner.py | 3 +- .../agentic_fabric/runners/strands_runner.py | 3 +- .../tests/test_decomposer_edge_cases.py | 16 +++---- packages/agentic-fabric/tests/test_runners.py | 6 +-- 10 files changed, 124 insertions(+), 32 deletions(-) diff --git a/docs/agentic-workflows.rst b/docs/agentic-workflows.rst index 6318cca..6e6aa13 100644 --- a/docs/agentic-workflows.rst +++ b/docs/agentic-workflows.rst @@ -40,6 +40,30 @@ When no framework is installed, discovery and metadata inspection still work. Execution reports the unavailable runtime instead of failing during import. +Runtime Extras +-------------- + +The public runtime registry exposes install guidance for each optional +framework: + +.. code:: python + + from agentic_fabric import get_framework_info + + for runtime in get_framework_info(): + print(runtime["name"], runtime["available"], runtime["install"]) + +Supported runtime extras are ``crewai``, ``langgraph``, and ``strands``. +``ai`` installs all three. ``mcp`` and ``scraping`` are optional tool surfaces; +``all`` installs every optional runtime and tool dependency. Local CLI runners +are configured profiles over external executables and do not require a Python +extra: + +.. code:: bash + + agentic-fabric list-runners + agentic-fabric run --runner claude-code --input "Review this package" + Tool Resolution --------------- diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 14422a4..7dc92ed 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -15,6 +15,24 @@ Install the runtime extras you actually use: pip install "agentic-fabric[crewai]" pip install "agentic-fabric[langgraph]" pip install "agentic-fabric[strands]" + pip install "agentic-fabric[ai]" + pip install "agentic-fabric[mcp]" + pip install "agentic-fabric[scraping]" + pip install "agentic-fabric[all]" + +``ai`` installs the three agent framework runtimes. ``mcp`` exposes MCP +integration dependencies. ``scraping`` installs the CrewAI tools surface plus +``requests`` and ``beautifulsoup4``. ``all`` installs every optional runtime and +tool dependency declared by ``agentic-fabric``. Test, typing, docs, and dev +extras are reserved for repository validation and package maintenance. + +Local CLI runners do not require a Python extra. They shell out to external +executables that you install separately: + +.. code:: bash + + agentic-fabric list-runners --json + agentic-fabric run --runner aider --input "Add validation to auth.py" .. code:: python @@ -29,6 +47,11 @@ or ``.strands/`` directories. A ``.crew/`` directory is framework-agnostic. A framework-specific directory requires that runtime. +If a required runtime is unavailable, runtime errors include the matching +``agentic-fabric[...]`` install command. Automatic selection follows CrewAI, +then LangGraph, then Strands, but explicit runtime selection always wins unless +the crew is stored in a framework-specific directory. + Use ``AgenticData`` when runtime context should travel with data and registered crew definitions: @@ -39,3 +62,13 @@ registered crew definitions: session = AgenticData({"repo": "jbcom/agentic-fabric"}) session.register_agent("reviewer", crew_config) result = session.run_agent("reviewer", runtime="crewai") + +CrewAI Dependency Note +---------------------- + +The ``crewai``, ``ai``, ``scraping``, and ``all`` extras include dependencies +chosen by CrewAI. If a transitive CrewAI dependency has an upstream advisory +with no patched release, upgrading ``agentic-fabric`` or relaxing its CrewAI +range cannot clear that advisory by itself. Track the upstream CrewAI and +dependency advisory state before enabling those optional extras in sensitive +environments. diff --git a/docs/index.rst b/docs/index.rst index 99d7f1b..258bdbf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,6 +12,7 @@ LangGraph, Strands, or local CLI runners. pip install "agentic-fabric[crewai]" pip install "agentic-fabric[langgraph]" pip install "agentic-fabric[strands]" + pip install "agentic-fabric[ai]" Vendor-backed tools will be exposed as passthrough extras after ``vendor-fabric`` is available on PyPI. Until then, agent runtime work diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index 16b90c1..96d040c 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -26,9 +26,23 @@ pip install "agentic-fabric[strands]" # All frameworks pip install "agentic-fabric[ai]" -# Vendor-backed passthrough extras are added after vendor-fabric is published. +# Non-framework optional surfaces +pip install "agentic-fabric[mcp]" +pip install "agentic-fabric[scraping]" +pip install "agentic-fabric[all]" ``` +Local CLI runners do not require a Python extra. Install the external CLI +(`aider`, `claude`, `codex`, `ollama`, or a custom executable) and inspect +profiles with `agentic-fabric list-runners --json`. + +Vendor-backed passthrough extras are added after `vendor-fabric` is published. +Until then, vendor references stay lazy and report install guidance at use time. + +The `crewai`, `ai`, `scraping`, and `all` extras include dependencies selected +by CrewAI. If a transitive CrewAI dependency has an upstream advisory with no +patched release, upgrading `agentic-fabric` alone cannot clear that advisory. + ## Quick Start ### 1. Define a Crew (YAML) @@ -52,11 +66,16 @@ review_code: ### 2. Run It ```python -from agentic_fabric import run_crew_auto, detect_framework +from pathlib import Path + +from agentic_fabric import detect_framework, get_crew_config, run_crew_auto # See what framework is available framework = detect_framework() +# Load a crew manifest discovered in a package or workspace +config = get_crew_config(Path(".crew"), "analyzer") + # Auto-detect best framework and run result = run_crew_auto(config, inputs={"code": "..."}) ``` @@ -94,6 +113,8 @@ result = session.run_agent("reviewer", runtime="crewai") - Framework agnostic: one crew definition, multiple runtime backends. - Lazy imports: core package import does not require CrewAI, LangGraph, Strands, or vendor SDKs. +- Complete extras: `crewai`, `langgraph`, `strands`, `ai`, `mcp`, + `scraping`, `tests`, `typing`, `docs`, `dev`, and `all`. - `AgenticData`: carries data, registered crews, active runtime selection, and vendor-layer context together. - Capability decorators: runners and tools expose declared capabilities through @@ -117,6 +138,24 @@ result = session.run_agent("reviewer", runtime="crewai") You can always force a specific runner with `get_runner("langgraph")` or `agentic-fabric run --framework langgraph`. +If the selected runtime is not installed, errors point to the matching +`agentic-fabric[...]` extra. Framework-specific config directories also enforce +their runtime: a crew in `.langgraph/` will not silently run on CrewAI. + +## Local CLI Runners + +For single-agent coding tools, use the `--runner` CLI path: + +```bash +agentic-fabric list-runners --json +agentic-fabric run --runner aider --input "Add validation to auth.py" +agentic-fabric run --runner ollama --model deepseek-coder --input "Explain this module" +``` + +Profiles are loaded from the packaged `local_cli_profiles.yaml`, validated +before use, and rejected on POSIX systems if the profiles file is group- or +world-writable. + ## Repository Boundary - `extended-data` owns base data containers, logging, input handling, files, diff --git a/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py b/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py index c0a565c..fdfaeb6 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py @@ -89,12 +89,8 @@ def detect_framework(preferred: str | None = None) -> str: if is_framework_available(framework): return framework - raise RuntimeError( - "No AI frameworks installed. Install one of:\n" - " pip install crewai[tools]\n" - " pip install langgraph\n" - " pip install strands-agents" - ) + install_options = "\n".join(f" {_get_install_command(framework)}" for framework in FRAMEWORK_PRIORITY) + raise RuntimeError(f"No AI frameworks installed. Install one of:\n{install_options}") def get_available_frameworks() -> list[str]: @@ -237,7 +233,7 @@ def decompose_crew( if not is_framework_available(required_framework): raise RuntimeError( f"Crew requires {required_framework} but it's not installed. " - f"Install with: pip install {_get_install_command(required_framework)}" + f"Install with: {_get_install_command(required_framework)}" ) framework = required_framework @@ -246,13 +242,11 @@ def decompose_crew( def _get_install_command(framework: str) -> str: - """Get the pip install command for a framework.""" - install_commands = { - "crewai": "crewai[tools]", - "langgraph": "langgraph langchain-anthropic", - "strands": "strands-agents", - } - return install_commands.get(framework, framework) + """Get the package-extra install command for a framework.""" + try: + return get_runtime_spec(framework).install + except ValueError: + return f"pip install {framework}" # Convenience function for simple use cases @@ -290,7 +284,7 @@ def run_crew_auto( if not is_framework_available(required_framework): raise RuntimeError( f"Crew requires {required_framework} but it's not installed. " - f"Install with: pip install {_get_install_command(required_framework)}" + f"Install with: {_get_install_command(required_framework)}" ) framework = required_framework diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py index 86c68f2..c3f5d58 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py @@ -16,6 +16,7 @@ from typing import Any from agentic_fabric.runners.base import BaseRunner +from agentic_fabric.runners.registry import install_command from agentic_fabric.tools.registry import resolve_tools @@ -33,7 +34,7 @@ def __init__(self): try: import crewai # noqa: F401 except ImportError as e: - raise RuntimeError("CrewAI not installed. Install with: pip install crewai[tools]") from e + raise RuntimeError(f"CrewAI not installed. Install with: {install_command(self.framework_name)}") from e def build_crew(self, crew_config: dict[str, Any]) -> Any: """Build a CrewAI Crew from configuration. diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py index ca5f8bd..2906491 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py @@ -12,6 +12,7 @@ from typing import Any from agentic_fabric.runners.base import BaseRunner +from agentic_fabric.runners.registry import install_command from agentic_fabric.tools.adapters import resolve_langgraph_tools @@ -25,7 +26,7 @@ def __init__(self): try: import langgraph # noqa: F401 except ImportError as e: - raise RuntimeError("LangGraph not installed. Install with: pip install langgraph") from e + raise RuntimeError(f"LangGraph not installed. Install with: {install_command(self.framework_name)}") from e def build_crew(self, crew_config: dict[str, Any]) -> Any: """Build a LangGraph workflow from configuration. diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/strands_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/strands_runner.py index 5312dc0..dfb57d9 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/strands_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/strands_runner.py @@ -12,6 +12,7 @@ from typing import Any from agentic_fabric.runners.base import BaseRunner +from agentic_fabric.runners.registry import install_command from agentic_fabric.tools.adapters import resolve_strands_tools @@ -25,7 +26,7 @@ def __init__(self): try: import strands # noqa: F401 except ImportError as e: - raise RuntimeError("Strands not installed. Install with: pip install strands-agents") from e + raise RuntimeError(f"Strands not installed. Install with: {install_command(self.framework_name)}") from e def build_crew(self, crew_config: dict[str, Any]) -> Any: """Build a Strands agent from configuration. diff --git a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py index 31eaf18..4c98d61 100644 --- a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py +++ b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py @@ -127,7 +127,7 @@ def test_error_message_lists_install_options(self) -> None: """RuntimeError should include installation instructions.""" with ( patch("agentic_fabric.core.decomposer.is_framework_available", return_value=False), - pytest.raises(RuntimeError, match="pip install crewai"), + pytest.raises(RuntimeError, match=r"agentic-fabric\[crewai\]"), ): detect_framework() @@ -243,7 +243,7 @@ def test_decompose_crew_reports_conflicting_or_missing_required_runtime( monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda framework: False) - with pytest.raises(RuntimeError, match="pip install crewai\\[tools\\]"): + with pytest.raises(RuntimeError, match=r"agentic-fabric\[crewai\]"): decompose_crew(crew_config) def test_run_crew_auto_enforces_required_runtime_and_runs( @@ -278,7 +278,7 @@ def test_run_crew_auto_reports_conflicting_or_missing_required_runtime( monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda framework: False) - with pytest.raises(RuntimeError, match="pip install langgraph"): + with pytest.raises(RuntimeError, match=r"agentic-fabric\[langgraph\]"): run_crew_auto(crew_config) @@ -306,17 +306,15 @@ class TestGetInstallCommand: """Tests for _get_install_command.""" def test_crewai_includes_tools(self) -> None: - assert "crewai[tools]" in _get_install_command("crewai") + assert _get_install_command("crewai") == 'pip install "agentic-fabric[crewai]"' def test_langgraph_includes_langchain(self) -> None: - result = _get_install_command("langgraph") - assert "langgraph" in result - assert "langchain" in result + assert _get_install_command("langgraph") == 'pip install "agentic-fabric[langgraph]"' def test_strands_maps_correctly(self) -> None: - assert "strands-agents" in _get_install_command("strands") + assert _get_install_command("strands") == 'pip install "agentic-fabric[strands]"' def test_unknown_framework_returns_itself(self) -> None: """Unknown framework name should return as-is.""" result = _get_install_command("unknown_framework") - assert result == "unknown_framework" + assert result == "pip install unknown_framework" diff --git a/packages/agentic-fabric/tests/test_runners.py b/packages/agentic-fabric/tests/test_runners.py index 52ed188..bc960ea 100644 --- a/packages/agentic-fabric/tests/test_runners.py +++ b/packages/agentic-fabric/tests/test_runners.py @@ -40,7 +40,7 @@ def test_init_reports_missing_crewai(self, crew_mocker: CrewMocker) -> None: try: CrewAIRunner() except RuntimeError as exc: - assert "pip install crewai[tools]" in str(exc) + assert 'pip install "agentic-fabric[crewai]"' in str(exc) else: # pragma: no cover - defensive if CrewAI import mocking stops working raise AssertionError("CrewAIRunner did not report missing CrewAI") @@ -372,7 +372,7 @@ def test_init_reports_missing_langgraph(self, crew_mocker: CrewMocker) -> None: try: LangGraphRunner() except RuntimeError as exc: - assert "pip install langgraph" in str(exc) + assert 'pip install "agentic-fabric[langgraph]"' in str(exc) else: # pragma: no cover - defensive if LangGraph import mocking stops working raise AssertionError("LangGraphRunner did not report missing LangGraph") @@ -580,7 +580,7 @@ def test_init_reports_missing_strands(self, crew_mocker: CrewMocker) -> None: try: StrandsRunner() except RuntimeError as exc: - assert "pip install strands-agents" in str(exc) + assert 'pip install "agentic-fabric[strands]"' in str(exc) else: # pragma: no cover - defensive if Strands import mocking stops working raise AssertionError("StrandsRunner did not report missing Strands") From f28ec1771a4a90117c118d5b841edaee5479d23b Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 01:49:36 -0500 Subject: [PATCH 09/35] ci: align workspace release automation --- .github/dependabot.yml | 22 ++ .github/workflows/automerge.yml | 23 +++ .github/workflows/cd.yml | 95 +++++++-- .github/workflows/ci.yml | 39 ++-- .github/workflows/release.yml | 28 ++- docs/agentic-workflows.rst | 8 +- docs/getting-started.rst | 29 ++- docs/index.rst | 14 +- packages/agentic-fabric/README.md | 25 ++- .../examples/discovery_workflow.py | 4 +- packages/agentic-fabric/pyproject.toml | 19 +- .../src/agentic_fabric/__init__.py | 7 +- .../src/agentic_fabric/__main__.py | 2 +- .../src/agentic_fabric/capabilities.py | 9 +- .../src/agentic_fabric/config/llm.py | 13 +- .../src/agentic_fabric/core/discovery.py | 13 +- .../src/agentic_fabric/core/loader.py | 13 +- .../src/agentic_fabric/core/runner.py | 2 +- .../connector_builder_crew.py | 47 ++++- .../agentic-fabric/src/agentic_fabric/main.py | 47 +++-- .../agentic_fabric/runners/crewai_runner.py | 10 + .../runners/langgraph_runner.py | 10 +- .../runners/local_cli_runner.py | 14 +- .../src/agentic_fabric/tools/adapters.py | 5 + .../src/agentic_fabric/tools/file_tools.py | 27 ++- .../src/agentic_fabric/tools/registry.py | 6 +- .../agentic_fabric/tools/scraping_tools.py | 67 +++++- .../src/agentic_fabric/utils/files.py | 3 + packages/agentic-fabric/tests/conftest.py | 10 +- packages/agentic-fabric/tests/e2e/README.md | 2 +- .../agentic-fabric/tests/test_capabilities.py | 2 +- .../agentic-fabric/tests/test_cli_commands.py | 2 +- .../agentic-fabric/tests/test_cli_smoke.py | 5 +- .../tests/test_connector_builder_crew.py | 22 +- .../agentic-fabric/tests/test_core_runner.py | 2 +- .../agentic-fabric/tests/test_crew_mocker.py | 2 +- .../tests/test_decomposer_edge_cases.py | 1 - .../agentic-fabric/tests/test_discovery.py | 48 +++-- .../tests/test_file_tool_paths.py | 10 +- .../agentic-fabric/tests/test_file_tools.py | 47 +++-- .../agentic-fabric/tests/test_llm_config.py | 2 +- packages/agentic-fabric/tests/test_loader.py | 8 +- .../agentic-fabric/tests/test_loader_unit.py | 9 +- packages/agentic-fabric/tests/test_manager.py | 4 +- packages/agentic-fabric/tests/test_runners.py | 34 +++ .../tests/test_scraping_tool_crawler.py | 36 +++- .../tests/test_scraping_tools.py | 17 +- .../tests/test_single_agent_runners.py | 21 ++ .../tests/test_tool_adapters.py | 31 +++ .../tests/test_tool_registry.py | 6 + .../agentic-fabric/tests/test_utils_files.py | 11 + .../src/pytest_agentic_fabric/__init__.py | 7 +- pyproject.toml | 19 ++ release-please-config.json | 19 +- tox.ini | 42 +++- uv.lock | 194 ++++++++++++++---- 56 files changed, 925 insertions(+), 289 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/automerge.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c6e3433 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 +updates: + - package-ecosystem: pip + directory: / + schedule: + interval: weekly + groups: + minor-and-patch: + patterns: ["*"] + update-types: ["minor", "patch"] + ignore: + # ChromaDB is pulled only through optional CrewAI extras. As of 2026-06-27, + # the latest published ChromaDB release is still in the vulnerable range, + # so Dependabot cannot produce a patched update yet. + - dependency-name: chromadb + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + actions: + patterns: ["*"] diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 0000000..a6e4fac --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,23 @@ +name: Automerge + +# Enables GitHub native auto-merge on dependabot + release-please PRs. +# Native auto-merge waits for required checks + any required reviews, +# then squash-merges without further action. pull_request triggers fire +# from the target branch context, so secrets.CI_GITHUB_TOKEN is safe. +on: + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + +jobs: + automerge: + name: Enable auto-merge + runs-on: ubuntu-latest + if: > + github.event.pull_request.user.login == 'dependabot[bot]' || + startsWith(github.event.pull_request.head.ref, 'release-please--') + steps: + - name: Enable auto-merge (squash) + env: + GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: gh pr merge --auto --squash "$PR_URL" diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 677ff0b..a346e27 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -4,16 +4,66 @@ on: workflow_dispatch: inputs: tag: - description: Release tag to publish + description: Release tag to publish and deploy + required: true + type: string + package: + description: Workspace package to publish required: true type: string -permissions: - contents: read +permissions: {} jobs: - publish: - name: Publish package and docs + test: + name: Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + python-version: ["3.11", "3.12", "3.13", "3.14"] + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ inputs.tag }} + persist-credentials: false + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: ${{ matrix.python-version }} + - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + with: + version: "0.11.19" + enable-cache: true + - run: uv sync --all-packages --dev + - run: uv tool install tox --with tox-uv --with tox-gh + - run: tox -e "py$(echo '${{ matrix.python-version }}' | tr -d '.')" + + quality: + name: Quality, docs, and build + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ inputs.tag }} + persist-credentials: false + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: "3.13" + - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + with: + version: "0.11.19" + enable-cache: true + - run: uv sync --all-packages --dev + - run: uv tool install tox --with tox-uv --with tox-gh + - run: tox -e lint,typecheck,plugin,docs,build + + publish-package: + name: Publish package to PyPI + needs: [test, quality] runs-on: ubuntu-latest permissions: contents: read @@ -30,21 +80,24 @@ jobs: with: version: "0.11.19" enable-cache: true - - name: Verify + - name: Validate package input run: | - uv tool install tox --with tox-uv - tox -e lint,typecheck,py311,py312,py313,py314,plugin,docs,build -- -q - - name: Publish to PyPI - run: uv publish --trusted-publishing always + case "${{ inputs.package }}" in + agentic-fabric|pytest-agentic-fabric) ;; + *) echo "unsupported package: ${{ inputs.package }}" >&2; exit 1 ;; + esac + - run: uv build --package "${{ inputs.package }}" --out-dir dist --clear + - run: uv publish --trusted-publishing always dist/* - pages: + publish-docs: name: Publish docs to GitHub Pages - needs: publish + if: inputs.package == 'agentic-fabric' + needs: publish-package runs-on: ubuntu-latest permissions: contents: read - id-token: write pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} @@ -53,6 +106,9 @@ jobs: with: ref: ${{ inputs.tag }} persist-credentials: false + - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + with: + enablement: true - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: "3.13" @@ -60,15 +116,10 @@ jobs: with: version: "0.11.19" enable-cache: true - - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 - with: - enablement: true - - name: Build docs - run: | - uv tool install tox --with tox-uv - tox -e docs - - name: Upload Pages artifact - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + - run: uv sync --all-packages --dev + - run: uv tool install tox --with tox-uv --with tox-gh + - run: tox -e docs + - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: path: docs/_build/html - id: deployment diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6c44c8..981d25a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,44 +4,51 @@ on: push: branches: [main] pull_request: - types: [opened, synchronize, reopened, ready_for_review] + workflow_dispatch: -permissions: - contents: read +permissions: {} jobs: test: name: Python ${{ matrix.python-version }} runs-on: ubuntu-latest + permissions: + contents: read strategy: fail-fast: false matrix: - include: - - python-version: "3.11" - tox-env: py311 - - python-version: "3.12" - tox-env: py312 - - python-version: "3.13" - tox-env: py313 - - python-version: "3.14" - tox-env: py314 + python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: ${{ matrix.python-version }} - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - - name: Run tests - run: uvx --with tox-uv tox -e ${{ matrix.tox-env }} -- -q + with: + version: "0.11.19" + enable-cache: true + - run: uv sync --all-packages --dev + - run: uv tool install tox --with tox-uv --with tox-gh + - run: tox -e "py$(echo '${{ matrix.python-version }}' | tr -d '.')" quality: name: Quality, docs, build runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: "3.13" - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - - name: Run quality gates - run: uvx --with tox-uv tox -e lint,typecheck,plugin,docs,build + with: + version: "0.11.19" + enable-cache: true + - run: uv sync --all-packages --dev + - run: uv tool install tox --with tox-uv --with tox-gh + - run: tox -e lint,typecheck,plugin,docs,build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ffef7f1..98a82c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,25 +5,33 @@ on: branches: [main] workflow_dispatch: -permissions: - contents: write - pull-requests: write - actions: write +permissions: {} jobs: release-please: name: Release Please runs-on: ubuntu-latest + permissions: + actions: write + contents: write + pull-requests: write steps: - - id: release - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + id: release with: config-file: release-please-config.json manifest-file: .release-please-manifest.json - - name: Dispatch CD - if: steps.release.outputs.release_created == 'true' + - name: Dispatch agentic-fabric continuous delivery + if: steps.release.outputs['packages/agentic-fabric--release_created'] == 'true' env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} - TAG_NAME: ${{ steps.release.outputs.tag_name }} - run: gh workflow run cd.yml --repo "$GH_REPO" --ref main -f tag="$TAG_NAME" + RELEASE_TAG: ${{ steps.release.outputs['packages/agentic-fabric--tag_name'] }} + run: gh workflow run cd.yml --repo "$GH_REPO" --ref main -f tag="$RELEASE_TAG" -f package="agentic-fabric" + - name: Dispatch pytest-agentic-fabric continuous delivery + if: steps.release.outputs['packages/pytest-agentic-fabric--release_created'] == 'true' + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + RELEASE_TAG: ${{ steps.release.outputs['packages/pytest-agentic-fabric--tag_name'] }} + run: gh workflow run cd.yml --repo "$GH_REPO" --ref main -f tag="$RELEASE_TAG" -f package="pytest-agentic-fabric" diff --git a/docs/agentic-workflows.rst b/docs/agentic-workflows.rst index 6e6aa13..5afc8c8 100644 --- a/docs/agentic-workflows.rst +++ b/docs/agentic-workflows.rst @@ -54,10 +54,10 @@ framework: print(runtime["name"], runtime["available"], runtime["install"]) Supported runtime extras are ``crewai``, ``langgraph``, and ``strands``. -``ai`` installs all three. ``mcp`` and ``scraping`` are optional tool surfaces; -``all`` installs every optional runtime and tool dependency. Local CLI runners -are configured profiles over external executables and do not require a Python -extra: +Install the runtime you actually execute; the package does not publish an +aggregate AI or all-frameworks extra. ``mcp`` and ``scraping`` remain focused +optional tool surfaces. Local CLI runners are configured profiles over external +executables and do not require a Python extra: .. code:: bash diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 7dc92ed..e183415 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -1,8 +1,8 @@ Getting Started =============== -Install the core package when you only need discovery, configuration -loading, and lazy runner selection: +Install the core package when you only need discovery, configuration loading, +lazy runner selection, and local CLI runner profiles: .. code:: bash @@ -15,16 +15,15 @@ Install the runtime extras you actually use: pip install "agentic-fabric[crewai]" pip install "agentic-fabric[langgraph]" pip install "agentic-fabric[strands]" - pip install "agentic-fabric[ai]" pip install "agentic-fabric[mcp]" pip install "agentic-fabric[scraping]" - pip install "agentic-fabric[all]" -``ai`` installs the three agent framework runtimes. ``mcp`` exposes MCP -integration dependencies. ``scraping`` installs the CrewAI tools surface plus -``requests`` and ``beautifulsoup4``. ``all`` installs every optional runtime and -tool dependency declared by ``agentic-fabric``. Test, typing, docs, and dev -extras are reserved for repository validation and package maintenance. +``mcp`` exposes MCP integration dependencies. ``scraping`` installs the CrewAI +tools surface plus ``requests`` and ``beautifulsoup4``. Vendor passthrough +extras are deferred until ``vendor-fabric`` is published with a stable +optional-extra contract. There is no aggregate AI or all-frameworks extra. +Test, typing, docs, and dev extras are reserved for repository validation and +package maintenance. Local CLI runners do not require a Python extra. They shell out to external executables that you install separately: @@ -66,9 +65,9 @@ registered crew definitions: CrewAI Dependency Note ---------------------- -The ``crewai``, ``ai``, ``scraping``, and ``all`` extras include dependencies -chosen by CrewAI. If a transitive CrewAI dependency has an upstream advisory -with no patched release, upgrading ``agentic-fabric`` or relaxing its CrewAI -range cannot clear that advisory by itself. Track the upstream CrewAI and -dependency advisory state before enabling those optional extras in sensitive -environments. +The ``crewai`` and ``scraping`` extras include dependencies chosen by CrewAI. +If a transitive CrewAI dependency has an upstream advisory with no patched +release, upgrading ``agentic-fabric`` or relaxing its CrewAI range cannot clear +that advisory by itself. Core and local CLI installs do not include CrewAI. +Track the upstream CrewAI and dependency advisory state before enabling those +optional extras in sensitive environments. diff --git a/docs/index.rst b/docs/index.rst index 258bdbf..2efc753 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,11 +12,17 @@ LangGraph, Strands, or local CLI runners. pip install "agentic-fabric[crewai]" pip install "agentic-fabric[langgraph]" pip install "agentic-fabric[strands]" - pip install "agentic-fabric[ai]" + pip install "agentic-fabric[mcp]" + pip install "agentic-fabric[scraping]" -Vendor-backed tools will be exposed as passthrough extras after -``vendor-fabric`` is available on PyPI. Until then, agent runtime work -stays separate from provider dependency declarations. +Local CLI runners are part of the core install because they shell out to +external executables and require no third-party Python framework. CrewAI, +LangGraph, Strands, MCP, scraping helpers, and vendor providers are opt-in +extras. + +Vendor-backed passthrough extras are deferred until ``vendor-fabric`` is +published with a stable optional-extra contract. Until then, vendor references +stay lazy and report install guidance at use time. Core imports stay lightweight. Optional frameworks and vendor SDKs are loaded only when a runner, tool, or adapter is resolved. diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index 96d040c..e0e5097 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -23,25 +23,24 @@ pip install "agentic-fabric[crewai]" pip install "agentic-fabric[langgraph]" pip install "agentic-fabric[strands]" -# All frameworks -pip install "agentic-fabric[ai]" - # Non-framework optional surfaces pip install "agentic-fabric[mcp]" pip install "agentic-fabric[scraping]" -pip install "agentic-fabric[all]" ``` Local CLI runners do not require a Python extra. Install the external CLI (`aider`, `claude`, `codex`, `ollama`, or a custom executable) and inspect profiles with `agentic-fabric list-runners --json`. -Vendor-backed passthrough extras are added after `vendor-fabric` is published. -Until then, vendor references stay lazy and report install guidance at use time. +Vendor-backed passthrough extras will be added after the upstream +`vendor-fabric` optional-extra contract is published and stable. Until then, +vendor references stay lazy and report install guidance at use time. -The `crewai`, `ai`, `scraping`, and `all` extras include dependencies selected -by CrewAI. If a transitive CrewAI dependency has an upstream advisory with no -patched release, upgrading `agentic-fabric` alone cannot clear that advisory. +There is no aggregate AI extra. Install exactly the framework or provider path +you use. The `crewai` and `scraping` extras include dependencies selected by +CrewAI. If a transitive CrewAI dependency has an upstream advisory with no +patched release, upgrading `agentic-fabric` alone cannot clear that advisory; +core/local-CLI installs are unaffected. ## Quick Start @@ -113,8 +112,8 @@ result = session.run_agent("reviewer", runtime="crewai") - Framework agnostic: one crew definition, multiple runtime backends. - Lazy imports: core package import does not require CrewAI, LangGraph, Strands, or vendor SDKs. -- Complete extras: `crewai`, `langgraph`, `strands`, `ai`, `mcp`, - `scraping`, `tests`, `typing`, `docs`, `dev`, and `all`. +- Focused extras: `crewai`, `langgraph`, `strands`, `mcp`, `scraping`, + `tests`, `typing`, `docs`, and `dev`. - `AgenticData`: carries data, registered crews, active runtime selection, and vendor-layer context together. - Capability decorators: runners and tools expose declared capabilities through @@ -125,8 +124,8 @@ result = session.run_agent("reviewer", runtime="crewai") - Hierarchical orchestration: `ManagerAgent` delegates across crews. - Package discovery: finds `.crew/`, `.crewai/`, `.langgraph/`, and `.strands/` directories. -- Vendor passthrough extras are planned after `vendor-fabric` is published to - PyPI. +- Vendor passthrough extras are deferred until `vendor-fabric` is published + with a stable optional-extra contract. - CLI and library: use from the command line or import as a module. ## Framework Priority diff --git a/packages/agentic-fabric/examples/discovery_workflow.py b/packages/agentic-fabric/examples/discovery_workflow.py index fb33473..264c2a3 100644 --- a/packages/agentic-fabric/examples/discovery_workflow.py +++ b/packages/agentic-fabric/examples/discovery_workflow.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import sys from pathlib import Path from typing import Any @@ -40,7 +41,8 @@ def summarize_workspace(workspace_root: Path = DEFAULT_WORKSPACE) -> dict[str, A def main() -> None: """Print the sample workspace summary as JSON.""" - print(json.dumps(summarize_workspace(), indent=2, sort_keys=True)) + sys.stdout.write(json.dumps(summarize_workspace(), indent=2, sort_keys=True)) + sys.stdout.write("\n") if __name__ == "__main__": diff --git a/packages/agentic-fabric/pyproject.toml b/packages/agentic-fabric/pyproject.toml index 0253ade..ac64c28 100644 --- a/packages/agentic-fabric/pyproject.toml +++ b/packages/agentic-fabric/pyproject.toml @@ -29,17 +29,11 @@ dependencies = [ ] [project.optional-dependencies] -crewai = ["crewai[tools,anthropic]>=1.5.0"] +crewai = ["crewai[anthropic]>=1.5.0"] langgraph = ["langgraph>=0.2.0", "langchain-anthropic>=0.3.0"] strands = ["strands-agents>=0.1.0"] -ai = [ - "crewai[tools,anthropic]>=1.5.0", - "langgraph>=0.2.0", - "langchain-anthropic>=0.3.0", - "strands-agents>=0.1.0", -] mcp = ["mcp>=1.0.0"] -scraping = ["crewai[tools]>=1.5.0", "beautifulsoup4>=4.12.0", "requests>=2.31.0"] +scraping = ["beautifulsoup4>=4.12.0", "crewai-tools>=1.5.0", "requests>=2.31.0"] tests = [ "coverage[toml]>=7.6.0", "pytest-asyncio>=0.23.0", @@ -65,15 +59,6 @@ docs = [ "sphinx-autodoc2>=0.5.0", "sphinx-copybutton>=0.5.2", ] -all = [ - "crewai[tools,anthropic]>=1.5.0", - "langgraph>=0.2.0", - "langchain-anthropic>=0.3.0", - "strands-agents>=0.1.0", - "mcp>=1.0.0", - "beautifulsoup4>=4.12.0", - "requests>=2.31.0", -] [project.scripts] agentic-fabric = "agentic_fabric.main:main" diff --git a/packages/agentic-fabric/src/agentic_fabric/__init__.py b/packages/agentic-fabric/src/agentic_fabric/__init__.py index 1653ed9..f8ab67a 100644 --- a/packages/agentic-fabric/src/agentic_fabric/__init__.py +++ b/packages/agentic-fabric/src/agentic_fabric/__init__.py @@ -32,8 +32,13 @@ async def execute_workflow(self, task): from __future__ import annotations +from importlib.metadata import PackageNotFoundError, version -__version__ = "1.0.0" + +try: + __version__ = version("agentic-fabric") +except PackageNotFoundError: + __version__ = "0.0.0" # Core exports - framework-agnostic functionality from agentic_fabric.agentic_data import AgenticData diff --git a/packages/agentic-fabric/src/agentic_fabric/__main__.py b/packages/agentic-fabric/src/agentic_fabric/__main__.py index 596e6e4..a6b8c51 100644 --- a/packages/agentic-fabric/src/agentic_fabric/__main__.py +++ b/packages/agentic-fabric/src/agentic_fabric/__main__.py @@ -1,4 +1,4 @@ -"""Allow running as: python -m crew_agents""" +"""Allow running as: python -m agentic_fabric.""" from __future__ import annotations diff --git a/packages/agentic-fabric/src/agentic_fabric/capabilities.py b/packages/agentic-fabric/src/agentic_fabric/capabilities.py index 00682f0..77ddc6a 100644 --- a/packages/agentic-fabric/src/agentic_fabric/capabilities.py +++ b/packages/agentic-fabric/src/agentic_fabric/capabilities.py @@ -101,7 +101,14 @@ def __init_subclass__(cls, **kwargs: Any) -> None: @classmethod def list_capabilities(cls, *, kind: str | None = None) -> tuple[AgentCapabilitySpec, ...]: """Return declared capabilities, optionally filtered by kind.""" - capabilities = list(cls.agent_capabilities.values()) + seen: set[int] = set() + capabilities = [] + for capability in cls.agent_capabilities.values(): + marker = id(capability) + if marker in seen: + continue + seen.add(marker) + capabilities.append(capability) if kind is not None: capabilities = [capability for capability in capabilities if capability.kind == kind] return tuple(capabilities) diff --git a/packages/agentic-fabric/src/agentic_fabric/config/llm.py b/packages/agentic-fabric/src/agentic_fabric/config/llm.py index 80c049c..0ed1561 100644 --- a/packages/agentic-fabric/src/agentic_fabric/config/llm.py +++ b/packages/agentic-fabric/src/agentic_fabric/config/llm.py @@ -55,6 +55,17 @@ class LLMConfig: "openrouter-haiku": "openrouter/anthropic/claude-haiku-4.5", } +_OPENROUTER_MODEL_MAP = { + _CLAUDE_HAIKU_45: "openrouter/anthropic/claude-haiku-4.5", + _CLAUDE_SONNET_45: "openrouter/anthropic/claude-sonnet-4.5", + _CLAUDE_SONNET_4: "openrouter/anthropic/claude-sonnet-4", + _CLAUDE_OPUS_4: "openrouter/anthropic/claude-opus-4", + "haiku": "openrouter/anthropic/claude-haiku-4.5", + "sonnet": "openrouter/anthropic/claude-sonnet-4.5", + "sonnet-4": "openrouter/anthropic/claude-sonnet-4", + "opus": "openrouter/anthropic/claude-opus-4", +} + # Predefined configurations for common use cases LLM_CONFIGS = { "reasoning": LLMConfig(model=_CLAUDE_OPUS_4, temperature=0.3, description="Optimized for complex reasoning tasks"), @@ -147,7 +158,7 @@ def _create_openrouter_llm(model: str, temperature: float, api_key: str) -> LLM: """Create OpenRouter LLM instance.""" # Convert model name to OpenRouter format if needed if not model.startswith("openrouter/"): - model = MODELS.get("openrouter-auto", "openrouter/auto") + model = _OPENROUTER_MODEL_MAP.get(model, MODELS.get(model, MODELS.get("openrouter-auto", "openrouter/auto"))) return LLM( model=model, diff --git a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py index 9f4a79e..1b01175 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py @@ -122,7 +122,7 @@ def discover_all_framework_configs( Returns: Dict mapping package name to dict of framework -> config_dir. - Example: {"otterfall": {"crewai": Path(...), "strands": Path(...)}} + Example: {"sample": {"crewai": Path(...), "strands": Path(...)}} Framework can be None for framework-agnostic .crew/ directories. """ if workspace_root is None: @@ -229,9 +229,16 @@ def get_crew_config(config_dir: Path, crew_name: str) -> dict: available = list(crews.keys()) raise ValueError(f"Crew '{crew_name}' not found. Available: {available}") + agents_rel = crew_config.get("agents") + tasks_rel = crew_config.get("tasks") + if not agents_rel or not tasks_rel: + missing = "agents" if not agents_rel else "tasks" + msg = f"Crew '{crew_name}' is missing required key: {missing}" + raise ValueError(msg) + # Load agents and tasks YAML - agents_path = _resolve_config_path(config_dir, crew_config["agents"]) - tasks_path = _resolve_config_path(config_dir, crew_config["tasks"]) + agents_path = _resolve_config_path(config_dir, agents_rel) + tasks_path = _resolve_config_path(config_dir, tasks_rel) agents = yaml.safe_load(agents_path.read_text(encoding="utf-8")) if agents_path.exists() else {} tasks = yaml.safe_load(tasks_path.read_text(encoding="utf-8")) if tasks_path.exists() else {} diff --git a/packages/agentic-fabric/src/agentic_fabric/core/loader.py b/packages/agentic-fabric/src/agentic_fabric/core/loader.py index 389bee5..3ea8a4f 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/loader.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/loader.py @@ -22,6 +22,15 @@ logger = logging.getLogger(__name__) +def _has_non_whitespace_content(file_path: Path) -> bool: + """Return whether a text file contains any non-whitespace content.""" + with file_path.open(encoding="utf-8") as handle: + while chunk := handle.read(4096): + if chunk.strip(): + return True + return False + + def load_knowledge_sources(knowledge_paths: list[Path]) -> list: """Load knowledge sources from the specified paths. @@ -46,9 +55,7 @@ def load_knowledge_sources(knowledge_paths: list[Path]) -> list: for ext in ["*.md", "*.ts", "*.tsx", "*.py"]: for file_path in knowledge_path.rglob(ext): try: - # Read file content directly - content = file_path.read_text(encoding="utf-8") - if content.strip(): + if _has_non_whitespace_content(file_path): sources.append( TextFileKnowledgeSource( file_paths=[str(file_path)], diff --git a/packages/agentic-fabric/src/agentic_fabric/core/runner.py b/packages/agentic-fabric/src/agentic_fabric/core/runner.py index 2652255..baf56ea 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/runner.py @@ -17,7 +17,7 @@ def run_crew( """Run a crew from a package with the given inputs. Args: - package_name: Name of the package (e.g., 'otterfall'). + package_name: Name of the package. crew_name: Name of the crew to run (e.g., 'game_builder'). inputs: Optional dict of inputs to pass to the crew. workspace_root: Optional workspace root path. diff --git a/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/connector_builder_crew.py b/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/connector_builder_crew.py index 9ae4ee4..591b5a3 100644 --- a/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/connector_builder_crew.py +++ b/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/connector_builder_crew.py @@ -8,13 +8,41 @@ from __future__ import annotations from pathlib import Path +from typing import Any -from crewai import Agent, Crew, Task - +from agentic_fabric.runners.registry import install_command from agentic_fabric.tools.registry import resolve_tools from agentic_fabric.utils import load_config +Agent: Any | None = None +Crew: Any | None = None +Task: Any | None = None + + +def _load_crewai_classes() -> tuple[Any, Any, Any]: + """Load CrewAI classes only when the connector-builder crew is used.""" + global Agent, Crew, Task + + if Agent is None or Crew is None or Task is None: + try: + from crewai import Agent as ImportedAgent + from crewai import Crew as ImportedCrew + from crewai import Task as ImportedTask + except ImportError as exc: + msg = f"ConnectorBuilderCrew requires CrewAI. Install with: {install_command('crewai')}" + raise RuntimeError(msg) from exc + + if Agent is None: + Agent = ImportedAgent + if Crew is None: + Crew = ImportedCrew + if Task is None: + Task = ImportedTask + + return Agent, Crew, Task + + class ConnectorBuilderCrew: """Manages the agents and tasks for the connector builder crew. @@ -38,14 +66,15 @@ def __init__(self, output_dir: str = "output"): output_dir: The directory where the generated connector code will be saved. """ + agent_cls, crew_cls, task_cls = _load_crewai_classes() config_dir = Path(__file__).parent / "config" agent_config = load_config(config_dir / "agents.yaml") task_config = load_config(config_dir / "tasks.yaml") - def build_agent(name: str) -> Agent: + def build_agent(name: str) -> Any: config = agent_config[name].copy() config["tools"] = resolve_tools(config.get("tools", [])) - return Agent(**config) + return agent_cls(**config) # Create Agents self.doc_scraper = build_agent("doc_scraper") @@ -53,17 +82,17 @@ def build_agent(name: str) -> Agent: self.code_generator = build_agent("code_generator") # Create Tasks - self.scrape_docs = Task(**task_config["scrape_docs"]) - self.analyze_api = Task(**task_config["analyze_api"]) + self.scrape_docs = task_cls(**task_config["scrape_docs"]) + self.analyze_api = task_cls(**task_config["analyze_api"]) generate_code_config = task_config["generate_code"].copy() generate_code_config["description"] = generate_code_config["description"].format(output_dir=output_dir) - self.generate_code = Task(**generate_code_config) + self.generate_code = task_cls(**generate_code_config) - self.crew = Crew( + self.crew = crew_cls( agents=[self.doc_scraper, self.api_analyzer, self.code_generator], tasks=[self.scrape_docs, self.analyze_api, self.generate_code], - verbose=2, + verbose=True, ) def kickoff(self, inputs: dict) -> str: diff --git a/packages/agentic-fabric/src/agentic_fabric/main.py b/packages/agentic-fabric/src/agentic_fabric/main.py index 6b0850a..b2488e2 100644 --- a/packages/agentic-fabric/src/agentic_fabric/main.py +++ b/packages/agentic-fabric/src/agentic_fabric/main.py @@ -118,13 +118,21 @@ def cmd_run(args): print(f"🚀 Running {args.package}/{args.crew}") print("=" * 60) - # Get input - if args.file: - input_text = Path(args.file).read_text(encoding="utf-8") - elif args.input: - input_text = args.input - else: - input_text = "" + try: + # Get input + if args.file: + input_text = Path(args.file).read_text(encoding="utf-8") + elif args.input: + input_text = args.input + else: + input_text = "" + except OSError as e: + duration_ms = int((time.time() - start_time) * 1000) + if use_json: + print(json.dumps({"success": False, "error": str(e), "duration_ms": duration_ms})) + else: + print(f"❌ Error: {e}") + sys.exit(2) inputs = {"spec": input_text, "component_spec": input_text, "input": input_text} @@ -168,7 +176,7 @@ def cmd_run(args): result = run_crew_auto( crew_config, inputs=inputs, - framework=requested, + framework=framework_used, ) duration_ms = int((time.time() - start_time) * 1000) @@ -216,12 +224,23 @@ def _cmd_run_single_agent(args, use_json: bool, start_time: float): print(f"🤖 Running single-agent: {args.runner}") print("=" * 60) - # Get input - if args.file: - input_text = Path(args.file).read_text(encoding="utf-8") - elif args.input: - input_text = args.input - else: + try: + # Get input + if args.file: + input_text = Path(args.file).read_text(encoding="utf-8") + elif args.input: + input_text = args.input + else: + input_text = "" + except OSError as e: + duration_ms = int((time.time() - start_time) * 1000) + if use_json: + print(json.dumps({"success": False, "error": str(e), "duration_ms": duration_ms})) + else: + print(f"❌ Error: {e}") + sys.exit(2) + + if not input_text: if use_json: print( json.dumps( diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py index c3f5d58..159a418 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py @@ -66,9 +66,19 @@ def build_crew(self, crew_config: dict[str, Any]) -> Any: # Build context from referenced tasks context_tasks = [] + unresolved_context = [] for ctx_name in task_cfg.get("context", []): if ctx_name in tasks_by_name: context_tasks.append(tasks_by_name[ctx_name]) + else: + unresolved_context.append(ctx_name) + + if unresolved_context: + logger.warning( + "Task '%s' references context tasks that are not yet available: %s", + task_name, + ", ".join(unresolved_context), + ) task = self.build_task(task_cfg, agents[agent_name], context=context_tasks) tasks.append(task) diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py index 2906491..b257fb7 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py @@ -29,16 +29,16 @@ def __init__(self): raise RuntimeError(f"LangGraph not installed. Install with: {install_command(self.framework_name)}") from e def build_crew(self, crew_config: dict[str, Any]) -> Any: - """Build a LangGraph workflow from configuration. + """Build a LangGraph ReAct agent from crew configuration. - Converts crew format to a LangGraph StateGraph with agents as nodes - and tasks as edges. + The current adapter condenses the universal crew configuration into a + LangGraph prebuilt ReAct agent with the declared tools attached. Args: crew_config: Universal crew configuration. Returns: - Compiled LangGraph StateGraph. + LangGraph runnable agent. """ from langgraph.prebuilt import create_react_agent @@ -89,7 +89,7 @@ def get_llm(self, model: str | None = None) -> Any: """ from langchain_anthropic import ChatAnthropic - # Default to Claude 3.5 Sonnet if no model specified + # Default to Claude Haiku 4.5 if no model specified. default_model = "claude-haiku-4-5-20251001" return ChatAnthropic(model=model or default_model) diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py index c5a4d2e..622d461 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py @@ -25,7 +25,7 @@ import stat import subprocess -from dataclasses import dataclass, field, fields +from dataclasses import dataclass, field, fields, replace from pathlib import Path from typing import Any @@ -80,6 +80,11 @@ class LocalCLIConfig: _CONFIG_FIELD_NAMES = {item.name for item in fields(LocalCLIConfig)} +def _copy_config(config: LocalCLIConfig) -> LocalCLIConfig: + """Return an isolated copy of a CLI config, including mutable list fields.""" + return replace(config, auth_env=list(config.auth_env), additional_flags=list(config.additional_flags)) + + def _validate_profile_config(name: str, config_dict: dict[str, Any]) -> dict[str, Any]: """Validate a local CLI profile loaded from YAML.""" unknown_fields = set(config_dict) - _CONFIG_FIELD_NAMES @@ -177,13 +182,16 @@ def __init__( f"Unknown profile '{profile}'. Available: {available}\n" f"Or provide a LocalCLIConfig object for custom tools." ) - self.config = profiles[profile] + self.config = _copy_config(profiles[profile]) elif isinstance(profile, dict): # Convert dict to LocalCLIConfig self.config = LocalCLIConfig(**_validate_profile_config("custom", profile)) - else: + elif isinstance(profile, LocalCLIConfig): # Use provided LocalCLIConfig directly self.config = profile + else: + msg = "profile must be a profile name, LocalCLIConfig, or config dict" + raise TypeError(msg) self.model = model self.runner_name = f"local_cli_{self.config.command}" diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/adapters.py b/packages/agentic-fabric/src/agentic_fabric/tools/adapters.py index 31ef1a1..e0dff34 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/adapters.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/adapters.py @@ -2,6 +2,7 @@ from __future__ import annotations +import contextlib import inspect import logging import re @@ -43,12 +44,16 @@ def _invoke_tool(tool_obj: Any, kwargs: dict[str, Any]) -> Any: def _build_runner(tool_obj: Any, name: str, description: str) -> Any: """Create a plain callable wrapper for a configured tool.""" + target = getattr(tool_obj, "_run", tool_obj) def runner(**kwargs: Any) -> Any: return _invoke_tool(tool_obj, kwargs) runner.__name__ = name runner.__doc__ = description + if callable(target): + with contextlib.suppress(ValueError, TypeError): + runner.__signature__ = inspect.signature(target) # type: ignore[attr-defined] return runner diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py b/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py index 2b231d1..ce3969a 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py @@ -1,7 +1,7 @@ """File manipulation tools for CrewAI agents. -These tools enable agents to read and write code to specific directories -in game package codebases (e.g., packages/otterfall). +These tools enable agents to read and write code to specific directories in +workspace package codebases. """ from __future__ import annotations @@ -37,32 +37,39 @@ def get_workspace_root(package_name: str | None = None) -> Path: Args: package_name: Name of the target package. If not provided, - uses TARGET_PACKAGE environment variable, or defaults to "otterfall". + uses TARGET_PACKAGE environment variable, or the only package + directory when the workspace has exactly one package. Returns: Path to packages/ directory. """ # Determine the target package name if package_name is None: - package_name = os.environ.get("TARGET_PACKAGE", "otterfall") + package_name = os.environ.get("TARGET_PACKAGE") # Find workspace root using marker file search workspace_root = _find_workspace_root() if workspace_root: - target_dir = workspace_root / "packages" / package_name - if target_dir.exists(): - return target_dir + packages_dir = workspace_root / "packages" + if package_name is not None: + target_dir = packages_dir / package_name + if target_dir.exists(): + return target_dir + elif packages_dir.is_dir(): + package_dirs = sorted(path for path in packages_dir.iterdir() if path.is_dir()) + if len(package_dirs) == 1: + return package_dirs[0] # Fallback: try environment variable for root directory - env_root_var = f"{package_name.upper()}_ROOT" - if env_root_var in os.environ: + env_root_var = f"{package_name.upper()}_ROOT" if package_name else None + if env_root_var and env_root_var in os.environ: return Path(os.environ[env_root_var]).resolve() # Last fallback - current directory (shouldn't happen in normal use) return Path.cwd() -# Allowed directories for writing (relative to packages/otterfall) +# Allowed directories for writing relative to the selected package root. ALLOWED_WRITE_DIRS = [ "src/ecs", # ECS components, world, data "src/ecs/data", # Species definitions, etc. diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/registry.py b/packages/agentic-fabric/src/agentic_fabric/tools/registry.py index c71c429..8a702b6 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/registry.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/registry.py @@ -112,13 +112,13 @@ def resolve_tool(tool_name: str) -> Any | None: if vendor_tool is not None: return vendor_tool - if canonical_name in _TOOL_FACTORIES: - return _TOOL_FACTORIES[canonical_name]() - if canonical_name.startswith("mcp://"): return None try: + if canonical_name in _TOOL_FACTORIES: + return _TOOL_FACTORIES[canonical_name]() + if ":" in canonical_name: module_name, attr_name = canonical_name.split(":", 1) if not _is_import_allowed(module_name): diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py b/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py index a576328..d69d6b1 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py @@ -5,6 +5,7 @@ import logging from collections import deque +from ipaddress import ip_address from urllib.parse import urljoin, urlparse import requests @@ -15,6 +16,36 @@ log = logging.getLogger(__name__) +MAX_CRAWL_PAGES = 25 +MAX_CRAWL_DEPTH = 2 +_ALLOWED_SCHEMES = {"http", "https"} +_BLOCKED_HOSTS = {"localhost"} + + +def _is_safe_crawl_url(url: str) -> bool: + """Return whether a URL is safe for direct crawler requests.""" + parsed = urlparse(url) + if parsed.scheme not in _ALLOWED_SCHEMES or not parsed.hostname: + return False + + host = parsed.hostname.lower().rstrip(".") + if host in _BLOCKED_HOSTS or host.endswith(".localhost"): + return False + + try: + address = ip_address(host) + except ValueError: + return True + + return not ( + address.is_private + or address.is_loopback + or address.is_link_local + or address.is_multicast + or address.is_reserved + or address.is_unspecified + ) + class CrawlWebsiteTool(ScrapeWebsiteTool): """A tool for crawling a website and scraping its content. @@ -35,13 +66,19 @@ def _run(self, url: str) -> str: Returns: The scraped content of the website. """ - scraped_content = "" - visited_urls = set() - urls_to_visit = deque([url]) + if not _is_safe_crawl_url(url): + log.warning("Skipping unsafe crawl URL %s", url) + return "" + + scraped_content: list[str] = [] + visited_urls: set[str] = set() + queued_urls = {url} + urls_to_visit = deque([(url, 0)]) base_netloc = urlparse(url).netloc - while urls_to_visit: - current_url = urls_to_visit.popleft() + while urls_to_visit and len(visited_urls) < MAX_CRAWL_PAGES: + current_url, depth = urls_to_visit.popleft() + queued_urls.discard(current_url) if current_url in visited_urls: continue visited_urls.add(current_url) @@ -51,17 +88,29 @@ def _run(self, url: str) -> str: response.raise_for_status() soup = BeautifulSoup(response.content, "html.parser") - scraped_content += self._scrape_content(soup) + page_content = self._scrape_content(soup) + if page_content: + scraped_content.append(page_content) + + if depth >= MAX_CRAWL_DEPTH: + continue for link in soup.find_all("a", href=True): next_url = urljoin(current_url, link["href"]) - if urlparse(next_url).netloc == base_netloc and next_url not in visited_urls: - urls_to_visit.append(next_url) + if ( + next_url not in visited_urls + and next_url not in queued_urls + and len(visited_urls) + len(queued_urls) < MAX_CRAWL_PAGES + and urlparse(next_url).netloc == base_netloc + and _is_safe_crawl_url(next_url) + ): + urls_to_visit.append((next_url, depth + 1)) + queued_urls.add(next_url) except requests.RequestException: log.exception("Error crawling %s", current_url) - return scraped_content + return " ".join(scraped_content) def _scrape_content(self, soup: BeautifulSoup) -> str: """Scrapes the readable content from a BeautifulSoup object.""" diff --git a/packages/agentic-fabric/src/agentic_fabric/utils/files.py b/packages/agentic-fabric/src/agentic_fabric/utils/files.py index 08a28e5..2ed3391 100644 --- a/packages/agentic-fabric/src/agentic_fabric/utils/files.py +++ b/packages/agentic-fabric/src/agentic_fabric/utils/files.py @@ -19,4 +19,7 @@ def load_config(path: Path | str) -> dict[str, Any]: """ with open(path, encoding="utf-8") as f: loaded = yaml.safe_load(f) or {} + if not isinstance(loaded, dict): + msg = f"Expected mapping at top level of YAML config: {path}" + raise TypeError(msg) return cast(dict[str, Any], loaded) diff --git a/packages/agentic-fabric/tests/conftest.py b/packages/agentic-fabric/tests/conftest.py index be4e742..464bdb0 100644 --- a/packages/agentic-fabric/tests/conftest.py +++ b/packages/agentic-fabric/tests/conftest.py @@ -76,17 +76,17 @@ def temp_workspace(tmp_path: Path) -> Path: packages_dir = tmp_path / "packages" packages_dir.mkdir() - # Create a mock otterfall package with .crewai structure - otterfall_dir = packages_dir / "otterfall" - otterfall_dir.mkdir() + # Create a mock sample package with .crewai structure + sample_dir = packages_dir / "sample" + sample_dir.mkdir() - crewai_dir = otterfall_dir / ".crewai" + crewai_dir = sample_dir / ".crewai" crewai_dir.mkdir() # Create minimal manifest (dict format, not list) manifest = crewai_dir / "manifest.yaml" manifest.write_text(""" -name: otterfall +name: sample description: Test package crews: test_crew: diff --git a/packages/agentic-fabric/tests/e2e/README.md b/packages/agentic-fabric/tests/e2e/README.md index ee45ff7..8b7ccfd 100644 --- a/packages/agentic-fabric/tests/e2e/README.md +++ b/packages/agentic-fabric/tests/e2e/README.md @@ -82,7 +82,7 @@ crew_config = { ## Test Structure -``` +```text tests/e2e/ ├── conftest.py # E2E fixtures and pytest configuration ├── test_crewai_e2e.py # CrewAI runner E2E tests diff --git a/packages/agentic-fabric/tests/test_capabilities.py b/packages/agentic-fabric/tests/test_capabilities.py index 90e05b5..cae96f7 100644 --- a/packages/agentic-fabric/tests/test_capabilities.py +++ b/packages/agentic-fabric/tests/test_capabilities.py @@ -72,7 +72,7 @@ def read_file(self) -> str: provider = ToolProvider() specs = provider.list_capabilities(kind="tool") - assert specs == (provider.agent_capabilities["read_file"], provider.agent_capabilities["read"]) + assert specs == (provider.agent_capabilities["read_file"],) assert specs[0].as_dict() == { "name": "read_file", "kind": "tool", diff --git a/packages/agentic-fabric/tests/test_cli_commands.py b/packages/agentic-fabric/tests/test_cli_commands.py index fd5923e..f527823 100644 --- a/packages/agentic-fabric/tests/test_cli_commands.py +++ b/packages/agentic-fabric/tests/test_cli_commands.py @@ -106,7 +106,7 @@ def test_cmd_run_json_success_auto_detects_framework( run_crew_auto.assert_called_once_with( {"name": "reviewer"}, inputs={"spec": "review this", "component_spec": "review this", "input": "review this"}, - framework=None, + framework="crewai", ) diff --git a/packages/agentic-fabric/tests/test_cli_smoke.py b/packages/agentic-fabric/tests/test_cli_smoke.py index eaf492f..baccc46 100644 --- a/packages/agentic-fabric/tests/test_cli_smoke.py +++ b/packages/agentic-fabric/tests/test_cli_smoke.py @@ -28,11 +28,8 @@ def test_help_flag(self) -> None: main() assert exc_info.value.code == 0 - def test_list_command_with_no_packages(self, tmp_path, capsys) -> None: + def test_list_command_with_no_packages(self, capsys) -> None: """'list' should work even with no packages found.""" - # Create empty packages dir - (tmp_path / "packages").mkdir() - with ( patch("sys.argv", ["agentic-fabric", "list"]), patch( diff --git a/packages/agentic-fabric/tests/test_connector_builder_crew.py b/packages/agentic-fabric/tests/test_connector_builder_crew.py index 8d50fe0..d376955 100644 --- a/packages/agentic-fabric/tests/test_connector_builder_crew.py +++ b/packages/agentic-fabric/tests/test_connector_builder_crew.py @@ -61,9 +61,20 @@ def import_connector_builder_with_fake_crewai(monkeypatch: pytest.MonkeyPatch) - return importlib.import_module("agentic_fabric.crews.connector_builder.connector_builder_crew") -def test_connector_builder_crew_initializes_and_kicks_off(monkeypatch: pytest.MonkeyPatch) -> None: - """ConnectorBuilderCrew should build configured agents/tasks and delegate kickoff.""" +@pytest.fixture +def connector_builder_module(monkeypatch: pytest.MonkeyPatch): + """Yield a connector-builder module backed by fake CrewAI classes.""" module = import_connector_builder_with_fake_crewai(monkeypatch) + yield module + sys.modules.pop("agentic_fabric.crews.connector_builder.connector_builder_crew", None) + + +def test_connector_builder_crew_initializes_and_kicks_off( + monkeypatch: pytest.MonkeyPatch, + connector_builder_module: Any, +) -> None: + """ConnectorBuilderCrew should build configured agents/tasks and delegate kickoff.""" + module = connector_builder_module monkeypatch.setattr(module, "resolve_tools", lambda tools: [f"resolved:{tool}" for tool in tools]) crew_instance = module.ConnectorBuilderCrew(output_dir="test_output") @@ -91,9 +102,12 @@ def test_connector_builder_crew_initializes_and_kicks_off(monkeypatch: pytest.Mo assert crew_instance.crew.kickoff_inputs == {"url": "http://example.com"} -def test_connector_builder_kickoff_returns_raw_result(monkeypatch: pytest.MonkeyPatch) -> None: +def test_connector_builder_kickoff_returns_raw_result( + monkeypatch: pytest.MonkeyPatch, + connector_builder_module: Any, +) -> None: """CrewAI raw results should be returned without string conversion.""" - module = import_connector_builder_with_fake_crewai(monkeypatch) + module = connector_builder_module class RawCrew(FakeCrew): def kickoff(self, inputs: dict[str, Any]) -> Any: diff --git a/packages/agentic-fabric/tests/test_core_runner.py b/packages/agentic-fabric/tests/test_core_runner.py index 36444e6..5c6cecd 100644 --- a/packages/agentic-fabric/tests/test_core_runner.py +++ b/packages/agentic-fabric/tests/test_core_runner.py @@ -38,7 +38,7 @@ def kickoff(self, inputs: dict[str, Any]) -> Any: def test_run_crew_discovers_package_loads_config_and_returns_raw(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: crewai_dir = tmp_path / "pkg" / ".crewai" fake_crew = FakeCrew(FakeRawResult()) - calls: list[tuple[str, Any]] = [] + calls: list[tuple[Any, ...]] = [] monkeypatch.setattr(crew_runner, "discover_packages", lambda workspace_root: {"pkg": crewai_dir}) diff --git a/packages/agentic-fabric/tests/test_crew_mocker.py b/packages/agentic-fabric/tests/test_crew_mocker.py index dcf1181..16a2d44 100644 --- a/packages/agentic-fabric/tests/test_crew_mocker.py +++ b/packages/agentic-fabric/tests/test_crew_mocker.py @@ -70,4 +70,4 @@ class TestLocalFixtures: """Test reusable local fixtures.""" def test_temp_workspace_fixture(self, temp_workspace: Path) -> None: - assert (temp_workspace / "packages" / "otterfall" / ".crewai" / "manifest.yaml").exists() + assert (temp_workspace / "packages" / "sample" / ".crewai" / "manifest.yaml").exists() diff --git a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py index 4c98d61..986490b 100644 --- a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py +++ b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py @@ -28,7 +28,6 @@ def setup_method(self): """Clear the framework cache before each test.""" _framework_cache.clear() clear_runtime_cache() - clear_runtime_cache() def test_unsupported_framework_returns_false(self) -> None: """An unsupported framework name should return False immediately.""" diff --git a/packages/agentic-fabric/tests/test_discovery.py b/packages/agentic-fabric/tests/test_discovery.py index 6dd7328..87ed326 100644 --- a/packages/agentic-fabric/tests/test_discovery.py +++ b/packages/agentic-fabric/tests/test_discovery.py @@ -17,8 +17,8 @@ def test_discover_packages_finds_crewai_directories(self, temp_workspace: Path) packages = discover_packages(workspace_root=temp_workspace) - assert "otterfall" in packages - assert packages["otterfall"].exists() + assert "sample" in packages + assert packages["sample"].exists() def test_discover_packages_finds_crew_directories(self, tmp_path: Path) -> None: """Test that discover_packages finds framework-agnostic .crew directories.""" @@ -116,12 +116,12 @@ def test_list_crews_returns_crews_from_manifest(self, temp_workspace: Path) -> N with patch( "agentic_fabric.core.discovery.discover_packages", - return_value={"otterfall": temp_workspace / "packages" / "otterfall" / ".crewai"}, + return_value={"sample": temp_workspace / "packages" / "sample" / ".crewai"}, ): crews_by_package = list_crews() - assert "otterfall" in crews_by_package - crews = crews_by_package["otterfall"] + assert "sample" in crews_by_package + crews = crews_by_package["sample"] assert len(crews) == 1 assert crews[0]["name"] == "test_crew" @@ -131,11 +131,11 @@ def test_list_crews_filters_by_package_name(self, temp_workspace: Path) -> None: with patch( "agentic_fabric.core.discovery.discover_packages", - return_value={"otterfall": temp_workspace / "packages" / "otterfall" / ".crewai"}, + return_value={"sample": temp_workspace / "packages" / "sample" / ".crewai"}, ): - crews_by_package = list_crews(package_name="otterfall") + crews_by_package = list_crews(package_name="sample") - assert "otterfall" in crews_by_package + assert "sample" in crews_by_package assert len(crews_by_package) == 1 def test_list_crews_returns_empty_for_nonexistent_package(self, temp_workspace: Path) -> None: @@ -144,7 +144,7 @@ def test_list_crews_returns_empty_for_nonexistent_package(self, temp_workspace: with patch( "agentic_fabric.core.discovery.discover_packages", - return_value={"otterfall": temp_workspace / "packages" / "otterfall" / ".crewai"}, + return_value={"sample": temp_workspace / "packages" / "sample" / ".crewai"}, ): crews_by_package = list_crews(package_name="nonexistent") @@ -154,11 +154,11 @@ def test_load_manifest_parses_yaml(self, temp_workspace: Path) -> None: """Test that load_manifest parses YAML correctly.""" from agentic_fabric.core.discovery import load_manifest - crewai_dir = temp_workspace / "packages" / "otterfall" / ".crewai" + crewai_dir = temp_workspace / "packages" / "sample" / ".crewai" manifest = load_manifest(crewai_dir) assert manifest is not None - assert manifest.get("name") == "otterfall" + assert manifest.get("name") == "sample" assert "crews" in manifest def test_get_workspace_root_finds_root(self) -> None: @@ -196,11 +196,35 @@ def test_get_crew_config_includes_required_framework(self, temp_workspace: Path) """Test that get_crew_config includes required_framework field.""" from agentic_fabric.core.discovery import get_crew_config - crewai_dir = temp_workspace / "packages" / "otterfall" / ".crewai" + crewai_dir = temp_workspace / "packages" / "sample" / ".crewai" config = get_crew_config(crewai_dir, "test_crew") assert config["required_framework"] == "crewai" + def test_get_crew_config_requires_agents_and_tasks_keys(self, tmp_path: Path) -> None: + """Crew configs should fail before resolving missing YAML paths.""" + from agentic_fabric.core.discovery import get_crew_config + + crew_dir = tmp_path / ".crew" + crew_dir.mkdir() + (crew_dir / "manifest.yaml").write_text( + """ +name: missing-files +crews: + no_agents: + tasks: tasks.yaml + no_tasks: + agents: agents.yaml +""", + encoding="utf-8", + ) + + with pytest.raises(ValueError, match="missing required key: agents"): + get_crew_config(crew_dir, "no_agents") + + with pytest.raises(ValueError, match="missing required key: tasks"): + get_crew_config(crew_dir, "no_tasks") + class TestDecomposer: """Tests for the decomposer module.""" diff --git a/packages/agentic-fabric/tests/test_file_tool_paths.py b/packages/agentic-fabric/tests/test_file_tool_paths.py index 2a89cb4..c20ee1d 100644 --- a/packages/agentic-fabric/tests/test_file_tool_paths.py +++ b/packages/agentic-fabric/tests/test_file_tool_paths.py @@ -57,7 +57,7 @@ def test_workspace_root_resolution_uses_workspace_env_and_cwd( """Workspace discovery should fall back from package roots to env vars and cwd.""" file_tools = import_file_tools_with_fake_crewai(monkeypatch) workspace_root = tmp_path / "workspace" - target_package = workspace_root / "packages" / "otterfall" + target_package = workspace_root / "packages" / "sample" target_package.mkdir(parents=True) monkeypatch.setattr(file_tools, "_find_workspace_root", lambda: workspace_root) @@ -66,14 +66,14 @@ def test_workspace_root_resolution_uses_workspace_env_and_cwd( env_root = tmp_path / "env-root" env_root.mkdir() monkeypatch.setattr(file_tools, "_find_workspace_root", lambda: None) - monkeypatch.setenv("OTTERFALL_ROOT", str(env_root)) + monkeypatch.setenv("SAMPLE_ROOT", str(env_root)) - assert file_tools.get_workspace_root("otterfall") == env_root + assert file_tools.get_workspace_root("sample") == env_root - monkeypatch.delenv("OTTERFALL_ROOT") + monkeypatch.delenv("SAMPLE_ROOT") monkeypatch.chdir(tmp_path) - assert file_tools.get_workspace_root("otterfall") == tmp_path + assert file_tools.get_workspace_root("sample") == tmp_path def test_find_workspace_root_finds_repository_root(monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/packages/agentic-fabric/tests/test_file_tools.py b/packages/agentic-fabric/tests/test_file_tools.py index f6f0386..10a4735 100644 --- a/packages/agentic-fabric/tests/test_file_tools.py +++ b/packages/agentic-fabric/tests/test_file_tools.py @@ -6,6 +6,8 @@ from __future__ import annotations import os +import sys +import types from pathlib import Path from unittest.mock import patch @@ -13,8 +15,21 @@ import pytest -# Skip all tests if crewai not installed -pytest.importorskip("crewai", reason="crewai not installed") +@pytest.fixture(autouse=True) +def fake_crewai_base_tool(monkeypatch: pytest.MonkeyPatch): + """Import file_tools without requiring the optional CrewAI package.""" + fake_crewai = types.ModuleType("crewai") + fake_tools = types.ModuleType("crewai.tools") + + class BaseTool: + """Minimal stand-in for crewai.tools.BaseTool.""" + + fake_tools.BaseTool = BaseTool + monkeypatch.setitem(sys.modules, "crewai", fake_crewai) + monkeypatch.setitem(sys.modules, "crewai.tools", fake_tools) + sys.modules.pop("agentic_fabric.tools.file_tools", None) + yield + sys.modules.pop("agentic_fabric.tools.file_tools", None) class TestGetWorkspaceRoot: @@ -42,17 +57,16 @@ def test_uses_target_package_env_var(self, temp_workspace: Path) -> None: assert root == other_pkg - def test_defaults_to_otterfall(self, temp_workspace: Path) -> None: - """Test that package defaults to 'otterfall'.""" + def test_defaults_to_single_workspace_package(self, tmp_path: Path) -> None: + """A single package workspace should be selected without repo-specific names.""" from agentic_fabric.tools.file_tools import get_workspace_root - # Create otterfall package - otterfall = temp_workspace / "packages" / "otterfall" - otterfall.mkdir(parents=True, exist_ok=True) + target_package = tmp_path / "packages" / "sample" + target_package.mkdir(parents=True, exist_ok=True) with patch( "agentic_fabric.tools.file_tools._find_workspace_root", - return_value=temp_workspace, + return_value=tmp_path, ): # Remove TARGET_PACKAGE if set env = os.environ.copy() @@ -60,7 +74,7 @@ def test_defaults_to_otterfall(self, temp_workspace: Path) -> None: with patch.dict(os.environ, env, clear=True): root = get_workspace_root() - assert root.name == "otterfall" + assert root == target_package def test_explicit_package_name(self, temp_workspace: Path) -> None: """Test explicit package name parameter.""" @@ -126,14 +140,14 @@ def test_writes_to_allowed_directory(self, temp_workspace: Path) -> None: from agentic_fabric.tools.file_tools import GameCodeWriterTool # Create the allowed directory structure - ecs_dir = temp_workspace / "packages" / "otterfall" / "src" / "ecs" + ecs_dir = temp_workspace / "packages" / "sample" / "src" / "ecs" ecs_dir.mkdir(parents=True) tool = GameCodeWriterTool() with patch( "agentic_fabric.tools.file_tools.get_workspace_root", - return_value=temp_workspace / "packages" / "otterfall", + return_value=temp_workspace / "packages" / "sample", ): result = tool._run( file_path="src/ecs/TestComponent.ts", @@ -142,6 +156,7 @@ def test_writes_to_allowed_directory(self, temp_workspace: Path) -> None: assert "Successfully wrote" in result assert (ecs_dir / "TestComponent.ts").exists() + assert (ecs_dir / "TestComponent.ts").read_text(encoding="utf-8") == "export const TestComponent = {};" class TestGameCodeReaderTool: @@ -162,7 +177,7 @@ def test_reads_existing_file(self, temp_workspace: Path) -> None: from agentic_fabric.tools.file_tools import GameCodeReaderTool # Create a test file - test_file = temp_workspace / "packages" / "otterfall" / "src" / "test.ts" + test_file = temp_workspace / "packages" / "sample" / "src" / "test.ts" test_file.parent.mkdir(parents=True, exist_ok=True) test_file.write_text("export const Test = 'hello';") @@ -170,7 +185,7 @@ def test_reads_existing_file(self, temp_workspace: Path) -> None: with patch( "agentic_fabric.tools.file_tools.get_workspace_root", - return_value=temp_workspace / "packages" / "otterfall", + return_value=temp_workspace / "packages" / "sample", ): result = tool._run(file_path="src/test.ts") @@ -184,7 +199,7 @@ def test_returns_error_for_missing_file(self, temp_workspace: Path) -> None: with patch( "agentic_fabric.tools.file_tools.get_workspace_root", - return_value=temp_workspace / "packages" / "otterfall", + return_value=temp_workspace / "packages" / "sample", ): result = tool._run(file_path="src/nonexistent.ts") @@ -200,7 +215,7 @@ def test_lists_directory_contents(self, temp_workspace: Path) -> None: from agentic_fabric.tools.file_tools import DirectoryListTool # Create some test files - src_dir = temp_workspace / "packages" / "otterfall" / "src" + src_dir = temp_workspace / "packages" / "sample" / "src" src_dir.mkdir(parents=True) (src_dir / "index.ts").write_text("export {};") (src_dir / "utils").mkdir() @@ -209,7 +224,7 @@ def test_lists_directory_contents(self, temp_workspace: Path) -> None: with patch( "agentic_fabric.tools.file_tools.get_workspace_root", - return_value=temp_workspace / "packages" / "otterfall", + return_value=temp_workspace / "packages" / "sample", ): result = tool._run(directory="src") diff --git a/packages/agentic-fabric/tests/test_llm_config.py b/packages/agentic-fabric/tests/test_llm_config.py index eebceed..4f7a125 100644 --- a/packages/agentic-fabric/tests/test_llm_config.py +++ b/packages/agentic-fabric/tests/test_llm_config.py @@ -70,7 +70,7 @@ def test_get_llm_can_force_openrouter_and_normalizes_model(monkeypatch: pytest.M assert isinstance(result, FakeLLM) assert result.kwargs == { - "model": "openrouter/auto", + "model": "openrouter/anthropic/claude-sonnet-4", "api_key": "openrouter-key", "base_url": "https://openrouter.ai/api/v1", "temperature": 0.4, diff --git a/packages/agentic-fabric/tests/test_loader.py b/packages/agentic-fabric/tests/test_loader.py index 31c6dd3..c4cb471 100644 --- a/packages/agentic-fabric/tests/test_loader.py +++ b/packages/agentic-fabric/tests/test_loader.py @@ -14,11 +14,9 @@ if TYPE_CHECKING: from tests._crew_mocker import CrewMocker -# Skip all tests in this module if crewai is not installed -pytestmark = pytest.mark.skipif( - not pytest.importorskip("crewai", reason="crewai not installed"), - reason="crewai not installed", -) + +pytest.importorskip("crewai", reason="crewai not installed") +pytestmark = pytest.mark.crewai class TestCreateAgentFromConfig: diff --git a/packages/agentic-fabric/tests/test_loader_unit.py b/packages/agentic-fabric/tests/test_loader_unit.py index caca732..1c87979 100644 --- a/packages/agentic-fabric/tests/test_loader_unit.py +++ b/packages/agentic-fabric/tests/test_loader_unit.py @@ -117,13 +117,12 @@ def test_load_knowledge_sources_logs_unreadable_files( bad_file = knowledge_dir / "bad.md" bad_file.write_text("content", encoding="utf-8") - def broken_read_text(self: Path, *args: Any, **kwargs: Any) -> str: - if self == bad_file: + def broken_content_check(path: Path) -> bool: + if path == bad_file: raise OSError("boom") - return original_read_text(self, *args, **kwargs) + return True - original_read_text = Path.read_text - monkeypatch.setattr(Path, "read_text", broken_read_text) + monkeypatch.setattr(loader_with_fake_crewai, "_has_non_whitespace_content", broken_content_check) assert loader_with_fake_crewai.load_knowledge_sources([knowledge_dir]) == [] assert "Could not load knowledge source" in caplog.text diff --git a/packages/agentic-fabric/tests/test_manager.py b/packages/agentic-fabric/tests/test_manager.py index 56e4569..d148caf 100644 --- a/packages/agentic-fabric/tests/test_manager.py +++ b/packages/agentic-fabric/tests/test_manager.py @@ -256,10 +256,8 @@ def run_side_effect(config, inputs, framework=None): ] ) - assert len(results) == 2 # Verify correct results returned (order matches input order) - assert "Design done" in results - assert "Assets done" in results + assert results == ["Design done", "Assets done"] # Both should have been executed assert mock_run.call_count == 2 diff --git a/packages/agentic-fabric/tests/test_runners.py b/packages/agentic-fabric/tests/test_runners.py index bc960ea..3baaec8 100644 --- a/packages/agentic-fabric/tests/test_runners.py +++ b/packages/agentic-fabric/tests/test_runners.py @@ -271,6 +271,40 @@ def test_handles_task_context_dependencies(self, crew_mocker: CrewMocker) -> Non assert "context" in second_task_kwargs assert second_task_kwargs["context"] == [mock_task1] + def test_build_crew_warns_for_unresolved_context_dependencies( + self, + crew_mocker: CrewMocker, + caplog, + ) -> None: + """Unresolved task context references should be visible to callers.""" + crew_mocker.mock_crewai() + + from agentic_fabric.runners.crewai_runner import CrewAIRunner + + crew_mocker.patch_crewai_crew() + crew_mocker.patch_crewai_agent() + crew_mocker.patch_crewai_task() + crew_mocker.patch_crewai_process() + crew_mocker.patch_get_llm() + + runner = CrewAIRunner() + crew_config = { + "agents": {"agent1": {"role": "Agent", "goal": "Goal", "backstory": "Story"}}, + "tasks": { + "task1": { + "description": "Task 1", + "expected_output": "Output 1", + "agent": "agent1", + "context": ["later_task"], + } + }, + "knowledge_paths": [], + } + + runner.build_crew(crew_config) + + assert "references context tasks that are not yet available: later_task" in caplog.text + def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> None: """Tool names declared in agent config should be instantiated and attached.""" crew_mocker.mock_crewai() diff --git a/packages/agentic-fabric/tests/test_scraping_tool_crawler.py b/packages/agentic-fabric/tests/test_scraping_tool_crawler.py index ab929b9..77201f2 100644 --- a/packages/agentic-fabric/tests/test_scraping_tool_crawler.py +++ b/packages/agentic-fabric/tests/test_scraping_tool_crawler.py @@ -3,6 +3,7 @@ from __future__ import annotations import importlib +import re import sys import types @@ -36,13 +37,13 @@ def __call__(self, tags: list[str]) -> list[object]: return [] def find_all(self, tag: str, href: bool = False) -> list[FakeLink]: - if tag == "a" and href and "duplicate-fail" in self.content: - return [FakeLink(href="/fail"), FakeLink(href="/fail")] - return [] + if tag != "a" or not href: + return [] + return [FakeLink(href=match) for match in re.findall(r"]+href=['\"]([^'\"]+)['\"]", self.content)] @property def stripped_strings(self) -> list[str]: - return ["duplicate-fail"] if "duplicate-fail" in self.content else [] + return [part.strip() for part in re.sub(r"<[^>]+>", " ", self.content).split() if part.strip()] fake_crewai_tools.ScrapeWebsiteTool = ScrapeWebsiteTool fake_requests.RequestException = RequestError @@ -74,6 +75,33 @@ def test_crawler_marks_failed_url_visited_before_retry(monkeypatch: pytest.Monke assert scraping_tools.requests.get.call_count == 2 +@pytest.mark.parametrize("url", ["file:///etc/passwd", "http://127.0.0.1", "http://localhost:8000"]) +def test_crawler_rejects_unsafe_start_urls(monkeypatch: pytest.MonkeyPatch, url: str) -> None: + """Unsafe starting URLs should not be requested.""" + scraping_tools = import_scraping_tools(monkeypatch) + + assert scraping_tools.CrawlWebsiteTool()._run(url) == "" + scraping_tools.requests.get.assert_not_called() + + +def test_crawler_enforces_page_limit(monkeypatch: pytest.MonkeyPatch) -> None: + """Crawl breadth should stay bounded.""" + scraping_tools = import_scraping_tools(monkeypatch) + monkeypatch.setattr(scraping_tools, "MAX_CRAWL_PAGES", 3) + root_response = MagicMock() + root_response.content = b"".join([f"Page {index}".encode() for index in range(10)]) + root_response.raise_for_status.return_value = None + page_response = MagicMock() + page_response.content = b"

child page

" + page_response.raise_for_status.return_value = None + scraping_tools.requests.get.side_effect = [root_response, page_response, page_response] + + result = scraping_tools.CrawlWebsiteTool()._run("https://example.test") + + assert "Page" in result + assert scraping_tools.requests.get.call_count == 3 + + def test_scrape_content_removes_script_and_style_tags(monkeypatch: pytest.MonkeyPatch) -> None: """The scraper should remove script/style nodes before reading text.""" scraping_tools = import_scraping_tools(monkeypatch) diff --git a/packages/agentic-fabric/tests/test_scraping_tools.py b/packages/agentic-fabric/tests/test_scraping_tools.py index 2b2daea..a16a4df 100644 --- a/packages/agentic-fabric/tests/test_scraping_tools.py +++ b/packages/agentic-fabric/tests/test_scraping_tools.py @@ -2,19 +2,16 @@ from __future__ import annotations -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock import pytest +from tests.test_scraping_tool_crawler import import_scraping_tools -pytest.importorskip("crewai", reason="crewai not installed") -from agentic_fabric.tools.scraping_tools import CrawlWebsiteTool - - -@patch("agentic_fabric.tools.scraping_tools.requests.get") -def test_crawl_website_tool(mock_get: MagicMock): +def test_crawl_website_tool(monkeypatch: pytest.MonkeyPatch): """Tests that the CrawlWebsiteTool scrapes content and discovers links.""" + scraping_tools = import_scraping_tools(monkeypatch) mock_response_page1 = MagicMock() mock_response_page1.content = b'Page 2

Content 1

' mock_response_page1.raise_for_status.return_value = None @@ -23,12 +20,12 @@ def test_crawl_website_tool(mock_get: MagicMock): mock_response_page2.content = b"

Content 2

" mock_response_page2.raise_for_status.return_value = None - mock_get.side_effect = [mock_response_page1, mock_response_page2] + scraping_tools.requests.get.side_effect = [mock_response_page1, mock_response_page2] - tool = CrawlWebsiteTool() + tool = scraping_tools.CrawlWebsiteTool() result = tool._run("http://example.com") assert "Content 1" in result assert "Page 2" in result assert "Content 2" in result - assert mock_get.call_count == 2 + assert scraping_tools.requests.get.call_count == 2 diff --git a/packages/agentic-fabric/tests/test_single_agent_runners.py b/packages/agentic-fabric/tests/test_single_agent_runners.py index a296073..958775f 100644 --- a/packages/agentic-fabric/tests/test_single_agent_runners.py +++ b/packages/agentic-fabric/tests/test_single_agent_runners.py @@ -169,6 +169,22 @@ def test_init_with_profile_name(self, mock_profiles_file: Path): assert runner.config.command == "aider" assert runner.config.task_flag == "--message" + def test_named_profile_receives_isolated_config_copy(self, mock_profiles_file: Path): + """Mutating one named runner should not mutate the cached profile.""" + with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: + mock_path.return_value.parent = mock_profiles_file.parent + mock_path.return_value.__truediv__.return_value = mock_profiles_file + + LocalCLIRunner._profiles_cache = None + runner = LocalCLIRunner("aider") + + runner.config.auth_env.append("MUTATED") + runner.config.additional_flags.append("--mutated") + + cached = LocalCLIRunner._load_profiles()["aider"] + assert cached.auth_env == ["OPENAI_API_KEY"] + assert cached.additional_flags == ["--no-git"] + def test_init_with_unknown_profile(self, mock_profiles_file: Path): """Should raise ValueError for unknown profile.""" with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: @@ -255,6 +271,11 @@ def test_init_with_config_object(self): assert runner.config.command == "custom-tool" assert runner.config.task_flag == "--task" + def test_init_rejects_unsupported_profile_input(self): + """Unsupported profile inputs should fail with a clear type error.""" + with pytest.raises(TypeError, match="profile must be"): + LocalCLIRunner(["not", "supported"]) + def test_is_available_tool_exists(self): """Should return True if tool is in PATH.""" config = LocalCLIConfig(command="python", task_flag="--task") diff --git a/packages/agentic-fabric/tests/test_tool_adapters.py b/packages/agentic-fabric/tests/test_tool_adapters.py index d362fe5..7d0625a 100644 --- a/packages/agentic-fabric/tests/test_tool_adapters.py +++ b/packages/agentic-fabric/tests/test_tool_adapters.py @@ -50,6 +50,16 @@ def __call__(self, **kwargs: str) -> str: return f"called:{kwargs['value']}" +class SignatureTool: + """Tool without args_schema whose _run signature should be preserved.""" + + name = "signature_tool" + description = "Uses signature inference." + + def _run(self, file_path: str, mode: str = "r") -> str: + return f"{mode}:{file_path}" + + class FakeStructuredTool: """Minimal LangChain-style tool wrapper used for adapter tests.""" @@ -162,6 +172,21 @@ def test_preserves_existing_langgraph_tool_objects(self, mock_resolve_tools: Mag assert adapted == [existing_tool] + @patch("agentic_fabric.tools.adapters.resolve_tools") + def test_preserves_runner_signature_for_schema_inference(self, mock_resolve_tools: MagicMock) -> None: + """LangChain schema inference should see the wrapped tool's real arguments.""" + mock_resolve_tools.return_value = [SignatureTool()] + + fake_tools_module = ModuleType("langchain_core.tools") + fake_tools_module.StructuredTool = FakeStructuredTool + + with patch.dict(sys.modules, {"langchain_core.tools": fake_tools_module}): + adapted = resolve_langgraph_tools(["SignatureTool"]) + + wrapped = adapted[0] + assert wrapped.infer_schema is True + assert tuple(wrapped.func.__signature__.parameters) == ("file_path", "mode") + class TestStrandsToolAdapters: """Tests for Strands configured-tool wrapping.""" @@ -230,6 +255,12 @@ def test_build_runner_invokes_callable_tools(self) -> None: assert runner.__name__ == "callable_tool" assert runner.__doc__ == "Call a tool." + def test_build_runner_preserves_tool_signature(self) -> None: + runner = _build_runner(SignatureTool(), "signature_tool", "Uses signature inference.") + + assert runner(file_path="README.md", mode="rb") == "rb:README.md" + assert tuple(runner.__signature__.parameters) == ("file_path", "mode") + def test_invoke_tool_rejects_noncallable_objects(self) -> None: with pytest.raises(TypeError, match="not callable"): _invoke_tool(object(), {}) diff --git a/packages/agentic-fabric/tests/test_tool_registry.py b/packages/agentic-fabric/tests/test_tool_registry.py index 77d0711..9e946ab 100644 --- a/packages/agentic-fabric/tests/test_tool_registry.py +++ b/packages/agentic-fabric/tests/test_tool_registry.py @@ -32,6 +32,12 @@ def test_resolves_builtin_alias(self, mock_import_module: MagicMock) -> None: mock_tool_class.assert_called_once_with() assert result is mock_tool_instance + @patch("agentic_fabric.tools.registry.importlib.import_module", side_effect=ImportError("missing crewai")) + def test_builtin_alias_skips_missing_optional_dependency(self, mock_import_module: MagicMock) -> None: + """Built-in aliases should skip cleanly when their optional imports fail.""" + assert resolve_tool("FileWriteTool") is None + mock_import_module.assert_called_once_with("agentic_fabric.tools.file_tools") + @patch("agentic_fabric.tools.registry.importlib.import_module") def test_resolves_fully_qualified_reference(self, mock_import_module: MagicMock) -> None: """Allowed module:attribute references should be resolved dynamically.""" diff --git a/packages/agentic-fabric/tests/test_utils_files.py b/packages/agentic-fabric/tests/test_utils_files.py index bfbc041..54bf594 100644 --- a/packages/agentic-fabric/tests/test_utils_files.py +++ b/packages/agentic-fabric/tests/test_utils_files.py @@ -4,6 +4,8 @@ from pathlib import Path +import pytest + from agentic_fabric.utils.files import load_config @@ -13,3 +15,12 @@ def test_load_config_reads_utf8_yaml(tmp_path: Path) -> None: config_file.write_text("description: café\n", encoding="utf-8") assert load_config(config_file) == {"description": "café"} + + +def test_load_config_rejects_non_mapping_yaml(tmp_path: Path) -> None: + """Top-level YAML sequences should fail with a clear error.""" + config_file = tmp_path / "config.yaml" + config_file.write_text("- one\n- two\n", encoding="utf-8") + + with pytest.raises(TypeError, match="Expected mapping"): + load_config(config_file) diff --git a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/__init__.py b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/__init__.py index fb63f50..0806b1c 100644 --- a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/__init__.py +++ b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/__init__.py @@ -2,8 +2,13 @@ from __future__ import annotations +from importlib.metadata import PackageNotFoundError, version + from pytest_agentic_fabric.plugin import RUNTIME_MODULES __all__ = ["RUNTIME_MODULES", "__version__"] -__version__ = "0.1.0" +try: + __version__ = version("pytest-agentic-fabric") +except PackageNotFoundError: + __version__ = "0.0.0" diff --git a/pyproject.toml b/pyproject.toml index d15ef85..1f794ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,9 @@ +[project] +name = "agentic-fabric-workspace" +version = "0.0.0" +requires-python = ">=3.11" +dependencies = [] + [tool.uv] package = false @@ -6,3 +12,16 @@ members = [ "packages/agentic-fabric", "packages/pytest-agentic-fabric", ] + +[tool.uv.sources] +agentic-fabric = { workspace = true } +pytest-agentic-fabric = { workspace = true } + +[dependency-groups] +dev = [ + "mypy>=1.20.1", + "ruff>=0.8.0", + "tox>=4.11", + "tox-gh>=1.3", + "tox-uv>=1.11", +] diff --git a/release-please-config.json b/release-please-config.json index 5cb3955..f073705 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -1,15 +1,32 @@ { + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", "release-type": "python", + "bootstrap-sha": "91e85f3850ead128275d3ed15c8cab9dfe9e2b02", "packages": { "packages/agentic-fabric": { "package-name": "agentic-fabric", + "release-type": "python", + "changelog-path": "CHANGELOG.md", "component": "agentic-fabric", "include-component-in-tag": true }, "packages/pytest-agentic-fabric": { "package-name": "pytest-agentic-fabric", + "release-type": "python", + "changelog-path": "CHANGELOG.md", "component": "pytest-agentic-fabric", "include-component-in-tag": true } - } + }, + "changelog-sections": [ + { "type": "feat", "section": "Features" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "perf", "section": "Performance Improvements" }, + { "type": "deps", "section": "Dependencies", "hidden": true }, + { "type": "refactor", "section": "Code Refactoring", "hidden": true }, + { "type": "docs", "section": "Documentation", "hidden": true }, + { "type": "chore", "section": "Miscellaneous Chores", "hidden": true }, + { "type": "test", "section": "Tests", "hidden": true }, + { "type": "ci", "section": "Continuous Integration", "hidden": true } + ] } diff --git a/tox.ini b/tox.ini index 042d030..7bbdf86 100644 --- a/tox.ini +++ b/tox.ini @@ -1,22 +1,48 @@ [tox] -env_list = lint,typecheck,py311,py312,py313,py314,plugin,docs,build requires = - tox>=4.0 - tox-uv>=1.16 + tox>=4.11 + tox-uv>=1.11 + tox-gh>=1.3 +env_list = + lint + typecheck + py311 + py312 + py313 + py314 + plugin + docs + build +min_version = 4.11 +skip_missing_interpreters = false + +[gh] +python = + 3.11 = py311 + 3.12 = py312 + 3.13 = py313 + 3.14 = py314 [testenv] +description = Run agentic-fabric package tests runner = uv-venv-runner package = skip deps = -e packages/agentic-fabric[tests] commands = pytest packages/agentic-fabric/tests {posargs} +[testenv:py{311,312,313,314}] +description = Run agentic-fabric tests on Python {base_python} +commands = {[testenv]commands} + [testenv:lint] +description = Lint source, tests, examples, docs, and workflow docs runner = uv-venv-runner package = skip deps = ruff>=0.8.0 commands = ruff check packages/agentic-fabric/src packages/agentic-fabric/tests packages/agentic-fabric/examples packages/pytest-agentic-fabric/src packages/pytest-agentic-fabric/tests docs AGENTS.md README.md [testenv:typecheck] +description = Type-check public package code runner = uv-venv-runner package = skip deps = @@ -25,6 +51,7 @@ deps = commands = mypy --config-file packages/agentic-fabric/pyproject.toml packages/agentic-fabric/src packages/pytest-agentic-fabric/src [testenv:plugin] +description = Run pytest-agentic-fabric tests runner = uv-venv-runner package = skip deps = @@ -33,6 +60,7 @@ deps = commands = pytest packages/pytest-agentic-fabric/tests {posargs} [testenv:docs] +description = Build Sphinx docs and generated autodoc2 API reference runner = uv-venv-runner package = skip deps = -e packages/agentic-fabric[docs] @@ -40,7 +68,11 @@ commands_pre = python -c "import shutil; shutil.rmtree('docs/_build/html', ignor commands = sphinx-build -W -E -b html docs docs/_build/html [testenv:build] +description = Build source and wheel distributions for both publishable packages runner = uv-venv-runner package = skip -deps = uv>=0.11.19 -commands = uv build --all-packages --out-dir dist --clear +deps = +allowlist_externals = uv +commands = + uv build --package agentic-fabric --out-dir dist/agentic-fabric --clear + uv build --package pytest-agentic-fabric --out-dir dist/pytest-agentic-fabric --clear diff --git a/uv.lock b/uv.lock index abae270..4477b10 100644 --- a/uv.lock +++ b/uv.lock @@ -17,6 +17,7 @@ resolution-markers = [ [manifest] members = [ "agentic-fabric", + "agentic-fabric-workspace", "pytest-agentic-fabric", ] @@ -42,23 +43,8 @@ dependencies = [ ] [package.optional-dependencies] -ai = [ - { name = "crewai", extra = ["anthropic", "tools"] }, - { name = "langchain-anthropic" }, - { name = "langgraph" }, - { name = "strands-agents" }, -] -all = [ - { name = "beautifulsoup4" }, - { name = "crewai", extra = ["anthropic", "tools"] }, - { name = "langchain-anthropic" }, - { name = "langgraph" }, - { name = "mcp" }, - { name = "requests" }, - { name = "strands-agents" }, -] crewai = [ - { name = "crewai", extra = ["anthropic", "tools"] }, + { name = "crewai", extra = ["anthropic"] }, ] dev = [ { name = "coverage", extra = ["toml"] }, @@ -89,7 +75,7 @@ mcp = [ ] scraping = [ { name = "beautifulsoup4" }, - { name = "crewai", extra = ["tools"] }, + { name = "crewai-tools" }, { name = "requests" }, ] strands = [ @@ -112,22 +98,14 @@ typing = [ [package.metadata] requires-dist = [ - { name = "agentic-fabric", extras = ["tests", "typing"], marker = "extra == 'dev'" }, - { name = "beautifulsoup4", marker = "extra == 'all'", specifier = ">=4.12.0" }, + { name = "agentic-fabric", extras = ["tests", "typing"], marker = "extra == 'dev'", editable = "packages/agentic-fabric" }, { name = "beautifulsoup4", marker = "extra == 'scraping'", specifier = ">=4.12.0" }, { name = "coverage", extras = ["toml"], marker = "extra == 'tests'", specifier = ">=7.6.0" }, - { name = "crewai", extras = ["tools"], marker = "extra == 'scraping'", specifier = ">=1.5.0" }, - { name = "crewai", extras = ["tools", "anthropic"], marker = "extra == 'ai'", specifier = ">=1.5.0" }, - { name = "crewai", extras = ["tools", "anthropic"], marker = "extra == 'all'", specifier = ">=1.5.0" }, - { name = "crewai", extras = ["tools", "anthropic"], marker = "extra == 'crewai'", specifier = ">=1.5.0" }, + { name = "crewai", extras = ["anthropic"], marker = "extra == 'crewai'", specifier = ">=1.5.0" }, + { name = "crewai-tools", marker = "extra == 'scraping'", specifier = ">=1.5.0" }, { name = "furo", marker = "extra == 'docs'", specifier = ">=2025.12.19" }, - { name = "langchain-anthropic", marker = "extra == 'ai'", specifier = ">=0.3.0" }, - { name = "langchain-anthropic", marker = "extra == 'all'", specifier = ">=0.3.0" }, { name = "langchain-anthropic", marker = "extra == 'langgraph'", specifier = ">=0.3.0" }, - { name = "langgraph", marker = "extra == 'ai'", specifier = ">=0.2.0" }, - { name = "langgraph", marker = "extra == 'all'", specifier = ">=0.2.0" }, { name = "langgraph", marker = "extra == 'langgraph'", specifier = ">=0.2.0" }, - { name = "mcp", marker = "extra == 'all'", specifier = ">=1.0.0" }, { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.0.0" }, { name = "mypy", marker = "extra == 'typing'", specifier = ">=1.20.1" }, { name = "myst-parser", marker = "extra == 'docs'", specifier = ">=4.0.1,<6.0.0" }, @@ -139,19 +117,41 @@ requires-dist = [ { name = "pytest-timeout", marker = "extra == 'tests'", specifier = ">=2.4.0" }, { name = "pytest-xdist", marker = "extra == 'tests'", specifier = ">=3.6.1" }, { name = "pyyaml", specifier = ">=6.0.3" }, - { name = "requests", marker = "extra == 'all'", specifier = ">=2.31.0" }, { name = "requests", marker = "extra == 'scraping'", specifier = ">=2.31.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" }, { name = "sphinx", marker = "extra == 'docs'", specifier = ">=8.2.3,<9.0.0" }, { name = "sphinx-autodoc2", marker = "extra == 'docs'", specifier = ">=0.5.0" }, { name = "sphinx-copybutton", marker = "extra == 'docs'", specifier = ">=0.5.2" }, - { name = "strands-agents", marker = "extra == 'ai'", specifier = ">=0.1.0" }, - { name = "strands-agents", marker = "extra == 'all'", specifier = ">=0.1.0" }, { name = "strands-agents", marker = "extra == 'strands'", specifier = ">=0.1.0" }, { name = "types-pyyaml", marker = "extra == 'typing'", specifier = ">=6.0.12.20240724" }, { name = "types-requests", marker = "extra == 'typing'", specifier = ">=2.33.0.20260408" }, ] -provides-extras = ["crewai", "langgraph", "strands", "ai", "mcp", "scraping", "tests", "typing", "dev", "docs", "all"] +provides-extras = ["crewai", "langgraph", "strands", "mcp", "scraping", "tests", "typing", "dev", "docs"] + +[[package]] +name = "agentic-fabric-workspace" +version = "0.0.0" +source = { virtual = "." } + +[package.dev-dependencies] +dev = [ + { name = "mypy" }, + { name = "ruff" }, + { name = "tox" }, + { name = "tox-gh" }, + { name = "tox-uv" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "mypy", specifier = ">=1.20.1" }, + { name = "ruff", specifier = ">=0.8.0" }, + { name = "tox", specifier = ">=4.11" }, + { name = "tox-gh", specifier = ">=1.3" }, + { name = "tox-uv", specifier = ">=1.11" }, +] [[package]] name = "aiohappyeyeballs" @@ -562,6 +562,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" }, ] +[[package]] +name = "cachetools" +version = "7.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/8b/0d3945a13955303b81272f759a0331e54c5c793da455e6f5706b89d2639c/cachetools-7.1.4.tar.gz", hash = "sha256:437f55a4e0c1b01a4f3077cc470e6991d47430970e36fbcb77e2be0df4fc1cd6", size = 40085, upload-time = "2026-05-21T22:40:43.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/7b/1fc1c09cc0756cf25861a3be10565915953876da48bb228fb9a672b20a42/cachetools-7.1.4-py3-none-any.whl", hash = "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54", size = 16761, upload-time = "2026-05-21T22:40:41.845Z" }, +] + [[package]] name = "certifi" version = "2026.6.17" @@ -922,9 +931,6 @@ wheels = [ anthropic = [ { name = "anthropic" }, ] -tools = [ - { name = "crewai-tools" }, -] [[package]] name = "crewai-tools" @@ -1024,6 +1030,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, ] +[[package]] +name = "distlib" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, +] + [[package]] name = "distro" version = "1.9.0" @@ -3096,6 +3111,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, ] +[[package]] +name = "platformdirs" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -3674,6 +3698,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/83/c77dfeed04022e8930b08eedca2b6e5efed256ab3321396fde90066efb65/pypika-0.51.1-py2.py3-none-any.whl", hash = "sha256:77985b4d7ce71b9905255bf12468cf598349e98837c037541cfc240e528aec46", size = 60585, upload-time = "2026-02-04T11:27:46.251Z" }, ] +[[package]] +name = "pyproject-api" +version = "1.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/62/0fe346fe380b1aafaf819c8cb195d3241bb4f355f908e6339814131a830b/pyproject_api-1.10.1.tar.gz", hash = "sha256:c2b2726bd7aa9217b6c50b621fef5b2ae5def4d55b779c9e0694c15e0a8517ba", size = 23477, upload-time = "2026-05-28T14:22:14.049Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/d7/29e1e5e882f79133631f7bcace42d23db493f616463c157a1ab614bf69dd/pyproject_api-1.10.1-py3-none-any.whl", hash = "sha256:fa9e6f66c35b5017e909825d8f2b5d5482ea699d7be809d21c03bd1f7317f36a", size = 12992, upload-time = "2026-05-28T14:22:12.711Z" }, +] + [[package]] name = "pyproject-hooks" version = "1.2.0" @@ -3725,7 +3761,7 @@ requires-dist = [ { name = "mypy", marker = "extra == 'typing'", specifier = ">=1.20.1" }, { name = "pytest", specifier = ">=9.0.3" }, { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.0.3" }, - { name = "pytest-agentic-fabric", extras = ["tests", "typing"], marker = "extra == 'dev'" }, + { name = "pytest-agentic-fabric", extras = ["tests", "typing"], marker = "extra == 'dev'", editable = "packages/pytest-agentic-fabric" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" }, ] provides-extras = ["tests", "typing", "dev"] @@ -3806,6 +3842,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-discovery" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, +] + [[package]] name = "python-docx" version = "1.2.0" @@ -4655,6 +4704,64 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, ] +[[package]] +name = "tox" +version = "4.56.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "python-discovery" }, + { name = "tomli-w" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6a/d20d405fc6661902ff803a9fa048d8aae27597c3b5dc750369ded82d08f7/tox-4.56.1.tar.gz", hash = "sha256:db1c2610802553189cf40de251661d066a635ee0ed9bf2a60093b5f1a7f36ef8", size = 283155, upload-time = "2026-06-25T06:18:36.802Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/97/560a5dfde154619d9643b1e208119dddc29bbb35a38a4ce4d095c16cf8f0/tox-4.56.1-py3-none-any.whl", hash = "sha256:4d06b925c4dd67872099b39c5a46fba79a2169c5f6e32060f95a8b1181f0ef55", size = 216469, upload-time = "2026-06-25T06:18:35.229Z" }, +] + +[[package]] +name = "tox-gh" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tox" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/abaf6c1a524547b0b871129e1dad73e51bfa66e682ce8be353c8c3b41ac2/tox_gh-1.7.1.tar.gz", hash = "sha256:4f4871ee4091b41464f2df9ab1fcedb14a48accdecbb60d53297301a9d8984c6", size = 11084, upload-time = "2026-02-23T20:45:52.496Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/62/b5affbf88da680c5c063f3a0f42aaa028bffd66bc8f15c4436e543ef6d94/tox_gh-1.7.1-py3-none-any.whl", hash = "sha256:a82285b16a597516f9a24f20326b124ddc7924a8b70cb05a9192e1c53d7f701d", size = 9915, upload-time = "2026-02-23T20:45:51.271Z" }, +] + +[[package]] +name = "tox-uv" +version = "1.35.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tox-uv-bare" }, + { name = "uv" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/dc/6e9994c799bdbb309f829dd6b8d98764dd0757302f3433c380438a3a127b/tox_uv-1.35.2-py3-none-any.whl", hash = "sha256:2d99b0e3c782ba49e7cbe521c8d344758595961b17a3633738d67096641c1bde", size = 6565, upload-time = "2026-05-05T01:34:16.07Z" }, +] + +[[package]] +name = "tox-uv-bare" +version = "1.35.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tox" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/cb/168dc1ccf24e4065a9a0a33df55709ed2b5eb73bd2b13ddd53187e5dffb8/tox_uv_bare-1.35.2.tar.gz", hash = "sha256:49e28a804c97f23ea17e25859960c0fa78f35bccb7e14344cfd840e89a9aade9", size = 32333, upload-time = "2026-05-05T01:34:18.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/4a33dc81da39db7b31e5622333df361e8fe055b7ec636bd5fea762c9182d/tox_uv_bare-1.35.2-py3-none-any.whl", hash = "sha256:c0d590a41d1054a1ad0874e9e5943ff52402786e3d4599d8f8d37a65b566ef53", size = 22307, upload-time = "2026-05-05T01:34:17.681Z" }, +] + [[package]] name = "tqdm" version = "4.68.3" @@ -4908,6 +5015,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, ] +[[package]] +name = "virtualenv" +version = "21.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" }, +] + [[package]] name = "watchdog" version = "6.0.0" From 1c2725a9894f94a4e06bba29ecebf01f74e37110 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 01:56:41 -0500 Subject: [PATCH 10/35] feat: align vendor capability tool catalog --- AGENTIC_REINFORCEMENT.md | 46 +++++++++++++++ docs/architecture.rst | 9 ++- docs/pillars.rst | 4 +- docs/vendor-fabric.rst | 22 ++++++- packages/agentic-fabric/README.md | 9 ++- .../src/agentic_fabric/agentic_data.py | 11 ++++ .../src/agentic_fabric/tools/__init__.py | 5 ++ .../src/agentic_fabric/tools/vendor.py | 57 +++++++++++++++++++ .../agentic-fabric/tests/test_agentic_data.py | 21 +++++++ .../tests/test_tool_registry.py | 37 +++++++++++- .../agentic-fabric/tests/test_tools_init.py | 2 + 11 files changed, 213 insertions(+), 10 deletions(-) create mode 100644 AGENTIC_REINFORCEMENT.md diff --git a/AGENTIC_REINFORCEMENT.md b/AGENTIC_REINFORCEMENT.md new file mode 100644 index 0000000..f459450 --- /dev/null +++ b/AGENTIC_REINFORCEMENT.md @@ -0,0 +1,46 @@ +# Agentic Reinforcement + +This repository creates the runtime- and agent-aware superclass on top of +`VendorData`. + +## AgenticData Contract + +- `AgenticData` extends `VendorData`, not `ExtendedData` directly. +- Preserve vendor-layer semantics while adding runtime context, agent registry + behavior, runner dispatch, and agent-facing tool exposure. +- The local `_VendorDataBase` fallback is an importability shim, not a second + architecture. Keep it minimal and temporary in spirit. + +## What This Repository Owns + +- runtime discovery and selection +- runner registries and runtime adapters +- LangChain, CrewAI, LangGraph, Strands, and MCP-facing tool wrappers +- agent-facing tool catalogs built from vendor capabilities +- crew, session, and agent orchestration context + +## What This Repository Does Not Own + +- base data primitives +- generic file/workflow/logging primitives +- provider SDK integrations +- provider availability/install guidance beyond delegating to `vendor-fabric` +- reimplementation of the `VendorData` provider dispatch layer + +## Hand-Off From Vendor Fabric + +- Pull provider capability metadata and typed callables from `vendor-fabric`. +- Decide here how those capabilities become framework tools and runtime-visible + surfaces. +- If something exists only because an AI framework wants a specific wrapper + type, keep it here. +- Do not push AI-framework shims back down into `vendor-fabric`. + +## Superclass Role + +- `AgenticData` is the superclass downstream domain-specific agent/session/task + objects should extend. +- Downstream apps should build on `AgenticData` rather than recreating provider + and runtime composition from scratch. +- Agentic behavior is additive over the vendor layer, which is additive over + the base data layer. diff --git a/docs/architecture.rst b/docs/architecture.rst index 641011e..521cb4d 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -14,13 +14,15 @@ Boundary - runner adapters for optional agent frameworks - tool resolution for built-in, MCP-style, fully qualified, and vendor-backed tools +- agent-facing tool catalogs built from ``vendor-fabric`` capability + metadata - agent capability dispatch over the data and vendor layers - pytest support for agent runtime tests ``extended-data`` owns base data primitives, logging, input handling, files, redaction, and generic workflows. ``vendor-fabric`` owns vendor -API connectors, provider-backed sync, SecretSync for Python, and -vendor-backed tool adapters. +API connectors, provider-backed sync, SecretSync for Python, provider +capability metadata, and provider dispatch. AgenticData ----------- @@ -110,6 +112,9 @@ declared by ``vendor-fabric``. Lazy vendor tool references are supported with ``vendor://provider/operation`` and ``vendor:provider:operation`` names. +When ``vendor-fabric`` is installed, ``AgenticData.vendor_tools()`` reads +``VendorData.capabilities()`` and returns lazy agent-facing wrappers for +those provider operations. Testing Package --------------- diff --git a/docs/pillars.rst b/docs/pillars.rst index 9cebf5b..990083a 100644 --- a/docs/pillars.rst +++ b/docs/pillars.rst @@ -34,7 +34,9 @@ Clear Boundaries This package does not own data primitives or vendor APIs. It composes ``extended-data`` and ``vendor-fabric`` when those capabilities are -installed. +installed. Provider connectors and dispatch stay in ``vendor-fabric``; +framework-specific and agent-facing wrappers built from those capabilities +stay in this package. Testable Adapters ----------------- diff --git a/docs/vendor-fabric.rst b/docs/vendor-fabric.rst index 92fe961..863382a 100644 --- a/docs/vendor-fabric.rst +++ b/docs/vendor-fabric.rst @@ -5,9 +5,9 @@ Vendor Fabric Integration until ``vendor-fabric`` is published and resolvable from PyPI. The core package still imports without vendor SDKs installed. -The integration rule is simple: vendor IO and SecretSync behavior belong -in ``vendor-fabric``; crew orchestration and runtime selection belong -here. +The integration rule is simple: vendor IO, provider capability metadata, +and SecretSync behavior belong in ``vendor-fabric``; crew orchestration, +runtime selection, and agent-facing framework wrappers belong here. ``AgenticData`` subclasses ``VendorData`` when ``vendor-fabric`` is installed. Until then, it keeps runtime context importable and raises @@ -25,3 +25,19 @@ Vendor-backed tools use lazy references: Those wrappers route through ``AgenticData.call`` and ``VendorData`` capabilities. Agent code should not import cloud SDKs or provider clients directly. + +When ``vendor-fabric`` is installed, downstream agent/session classes can +also expose a provider catalog from the superclass: + +.. code:: python + + from agentic_fabric import AgenticData + + + session = AgenticData() + github_tools = session.vendor_tools("github", include_unavailable=False) + +``vendor_tools()`` reads ``VendorData.capabilities()`` and converts each +capability route into a lazy ``VendorCapabilityTool``. The provider +connectors, install availability, and dispatch behavior still come from +``vendor-fabric``. diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index e0e5097..c044aca 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -120,6 +120,9 @@ result = session.run_agent("reviewer", runtime="crewai") read-only metadata and deterministic dispatch. - Tool resolution: built-in, vendor URI, and registered factories are preferred; external dynamic imports require `AGENTIC_FABRIC_TOOL_IMPORT_ALLOWLIST`. +- Vendor tool catalogs: `AgenticData.vendor_tools()` adapts inherited + `VendorData` capability metadata into agent-facing tools without importing + provider SDKs directly. - YAML-first: crew configuration in YAML, not Python boilerplate. - Hierarchical orchestration: `ManagerAgent` delegates across crews. - Package discovery: finds `.crew/`, `.crewai/`, `.langgraph/`, and @@ -160,9 +163,9 @@ world-writable. - `extended-data` owns base data containers, logging, input handling, files, redaction, and generic workflows. - `vendor-fabric` owns vendor connectors, provider-backed sync, SecretSync for - Python, and vendor-backed tool adapters. -- `agentic-fabric` owns crew discovery, runner selection, framework adapters, and - orchestration. + Python, provider capability metadata, and provider dispatch. +- `agentic-fabric` owns crew discovery, runner selection, framework adapters, + agent-facing tool wrappers, and orchestration. Full guides and API documentation are published at [jonbogaty.com/agentic-fabric](https://jonbogaty.com/agentic-fabric/). diff --git a/packages/agentic-fabric/src/agentic_fabric/agentic_data.py b/packages/agentic-fabric/src/agentic_fabric/agentic_data.py index 1be8ccc..1126a96 100644 --- a/packages/agentic-fabric/src/agentic_fabric/agentic_data.py +++ b/packages/agentic-fabric/src/agentic_fabric/agentic_data.py @@ -210,6 +210,17 @@ def call_runtime(self, capability: str, *args: Any, runtime: str | None = None, runner = get_runner(selected_runtime) return runner.call_capability(capability, *args, **kwargs) + def vendor_tools( + self, + provider: str | None = None, + *, + include_unavailable: bool = True, + ) -> list[Any]: + """Return agent-facing tools built from inherited vendor capabilities.""" + from agentic_fabric.tools.vendor import vendor_capability_tools + + return vendor_capability_tools(self, provider=provider, include_unavailable=include_unavailable) + def __getattr__(self, name: str) -> Any: """Expose ``run_`` helpers while preserving VendorData dispatch.""" if name.startswith("run_"): diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/__init__.py b/packages/agentic-fabric/src/agentic_fabric/tools/__init__.py index 86fd871..e32ba32 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/__init__.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/__init__.py @@ -14,6 +14,7 @@ if TYPE_CHECKING: from agentic_fabric.tools.file_tools import DirectoryListTool, GameCodeReaderTool, GameCodeWriterTool from agentic_fabric.tools.scraping_tools import CrawlWebsiteTool, ScrapeWebsiteTool + from agentic_fabric.tools.vendor import VendorCapabilityTool, vendor_capability_tools def _load_attr(module_name: str, attr_name: str) -> Any: @@ -26,6 +27,8 @@ def __getattr__(name: str) -> Any: return _load_attr("agentic_fabric.tools.file_tools", name) if name in {"CrawlWebsiteTool", "ScrapeWebsiteTool"}: return _load_attr("agentic_fabric.tools.scraping_tools", name) + if name in {"VendorCapabilityTool", "vendor_capability_tools"}: + return _load_attr("agentic_fabric.tools.vendor", name) raise AttributeError(f"module {__name__!r} has no attribute {name!r}") @@ -69,7 +72,9 @@ def get_all_tools() -> list[Any]: "GameCodeReaderTool", "GameCodeWriterTool", "ScrapeWebsiteTool", + "VendorCapabilityTool", "get_all_tools", "get_file_tools", "get_scraping_tools", + "vendor_capability_tools", ] diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/vendor.py b/packages/agentic-fabric/src/agentic_fabric/tools/vendor.py index 91c431f..9050272 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/vendor.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/vendor.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import Mapping from typing import Any @@ -15,6 +16,7 @@ def __init__( *, data: Any | None = None, description: str | None = None, + metadata: Any | None = None, ) -> None: """Initialize a lazy wrapper for one provider operation.""" self.provider = provider @@ -22,6 +24,18 @@ def __init__( self.name = f"{provider}_{operation}".replace("-", "_") self.description = description or f"Run vendor-fabric operation {operation!r} on provider {provider!r}." self.data = data + self.metadata = metadata + + @classmethod + def from_metadata(cls, metadata: Any, *, data: Any | None = None) -> VendorCapabilityTool: + """Build a tool wrapper from one ``VendorData.capabilities()`` item.""" + provider = str(_metadata_value(metadata, "provider", "")).strip() + operation = str(_metadata_value(metadata, "operation", "")).strip() + if not provider or not operation: + msg = "Vendor capability metadata requires provider and operation" + raise ValueError(msg) + description = str(_metadata_value(metadata, "description", "")).strip() or None + return cls(provider, operation, data=data, description=description, metadata=metadata) def _run(self, **kwargs: Any) -> Any: """Execute the vendor operation with keyword arguments.""" @@ -35,3 +49,46 @@ def _run(self, **kwargs: Any) -> Any: def __call__(self, **kwargs: Any) -> Any: """Allow framework adapters to treat this as a plain callable.""" return self._run(**kwargs) + + +def vendor_capability_tools( + data: Any | None = None, + *, + provider: str | None = None, + include_unavailable: bool = True, +) -> list[VendorCapabilityTool]: + """Return agent-facing tools for capabilities exposed by ``VendorData``.""" + if data is None: + from agentic_fabric.agentic_data import AgenticData + + data = AgenticData() + + capabilities = getattr(data, "capabilities", None) + if not callable(capabilities): + return [] + + raw_capabilities = ( + capabilities(provider, include_unavailable=include_unavailable) + if provider is not None + else capabilities(include_unavailable=include_unavailable) + ) + + tools: list[VendorCapabilityTool] = [] + for capability in raw_capabilities: + try: + tools.append(VendorCapabilityTool.from_metadata(capability, data=data)) + except ValueError: + continue + return tools + + +def _metadata_value(metadata: Any, key: str, default: Any) -> Any: + """Read capability metadata from mappings or Extended Data-like objects.""" + if isinstance(metadata, Mapping): + return metadata.get(key, default) + + getter = getattr(metadata, "get", None) + if callable(getter): + return getter(key, default) + + return getattr(metadata, key, default) diff --git a/packages/agentic-fabric/tests/test_agentic_data.py b/packages/agentic-fabric/tests/test_agentic_data.py index 0db16b6..7a1e495 100644 --- a/packages/agentic-fabric/tests/test_agentic_data.py +++ b/packages/agentic-fabric/tests/test_agentic_data.py @@ -240,6 +240,27 @@ def call_capability(self, capability: str, *args: Any, **kwargs: Any) -> tuple[A assert AgenticData().call_runtime("run", 1, runtime="crewai", flag=True) == ("run", (1,), {"flag": True}) +def test_vendor_tools_delegate_to_vendor_capability_catalog(monkeypatch: pytest.MonkeyPatch) -> None: + """AgenticData should expose agent-facing tools from vendor capabilities.""" + calls: list[tuple[AgenticData, str | None, bool]] = [] + + def fake_vendor_capability_tools( + data: AgenticData, + *, + provider: str | None = None, + include_unavailable: bool = True, + ) -> list[str]: + calls.append((data, provider, include_unavailable)) + return ["tool"] + + monkeypatch.setattr("agentic_fabric.tools.vendor.vendor_capability_tools", fake_vendor_capability_tools) + + data = AgenticData() + + assert data.vendor_tools("github", include_unavailable=False) == ["tool"] + assert calls == [(data, "github", False)] + + def test_dynamic_helpers_and_missing_attributes(monkeypatch: pytest.MonkeyPatch) -> None: """Registered agents should appear as dynamic run_ helpers.""" monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: True) diff --git a/packages/agentic-fabric/tests/test_tool_registry.py b/packages/agentic-fabric/tests/test_tool_registry.py index 9e946ab..65b5cff 100644 --- a/packages/agentic-fabric/tests/test_tool_registry.py +++ b/packages/agentic-fabric/tests/test_tool_registry.py @@ -9,7 +9,7 @@ import pytest from agentic_fabric.tools.registry import register_tool_factory, resolve_tool, resolve_tools -from agentic_fabric.tools.vendor import VendorCapabilityTool +from agentic_fabric.tools.vendor import VendorCapabilityTool, vendor_capability_tools class TestResolveTool: @@ -160,6 +160,21 @@ def test_resolve_tools_skips_unresolved_unique_names(self) -> None: class TestVendorCapabilityTool: """Tests for the generic vendor-backed tool wrapper.""" + def test_builds_tool_from_vendor_capability_metadata(self) -> None: + """VendorData capability metadata should become a lazy tool wrapper.""" + metadata = { + "provider": "github", + "operation": "get_file", + "description": "Read a repository file.", + } + + tool = VendorCapabilityTool.from_metadata(metadata) + + assert tool.provider == "github" + assert tool.operation == "get_file" + assert tool.description == "Read a repository file." + assert tool.metadata is metadata + def test_runs_operation_through_supplied_data(self) -> None: """Supplied data facade should receive provider and kwargs.""" data = MagicMock() @@ -184,3 +199,23 @@ def __new__(cls) -> MagicMock: VendorCapabilityTool("github", "get_file")(path="README.md") fake_data.call.assert_called_once_with("get_file", "github", path="README.md") + + def test_vendor_capability_tools_reads_vendor_data_catalog(self) -> None: + """VendorData capabilities should be converted into agent tool wrappers.""" + data = MagicMock() + data.capabilities.return_value = [ + {"provider": "github", "operation": "get_file", "description": "Read files"}, + {"provider": "", "operation": "broken"}, + ] + + tools = vendor_capability_tools(data, provider="github", include_unavailable=False) + + data.capabilities.assert_called_once_with("github", include_unavailable=False) + assert len(tools) == 1 + assert tools[0].provider == "github" + assert tools[0].operation == "get_file" + assert tools[0].data is data + + def test_vendor_capability_tools_returns_empty_without_vendor_catalog(self) -> None: + """The import shim should not invent provider behavior.""" + assert vendor_capability_tools(object()) == [] diff --git a/packages/agentic-fabric/tests/test_tools_init.py b/packages/agentic-fabric/tests/test_tools_init.py index ec3371b..33ccbac 100644 --- a/packages/agentic-fabric/tests/test_tools_init.py +++ b/packages/agentic-fabric/tests/test_tools_init.py @@ -26,9 +26,11 @@ def fake_load_attr(module_name: str, attr_name: str) -> type[FakeTool]: assert tools.__getattr__("GameCodeReaderTool") is FakeTool assert tools.__getattr__("ScrapeWebsiteTool") is FakeTool + assert tools.__getattr__("VendorCapabilityTool") is FakeTool assert calls == [ ("agentic_fabric.tools.file_tools", "GameCodeReaderTool"), ("agentic_fabric.tools.scraping_tools", "ScrapeWebsiteTool"), + ("agentic_fabric.tools.vendor", "VendorCapabilityTool"), ] From 50ba35fd3784dfbe34b52342a4661f1d498149c8 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 01:58:35 -0500 Subject: [PATCH 11/35] docs: define secrets sync boundary --- AGENTIC_REINFORCEMENT.md | 2 ++ AGENTS.md | 4 +++- SECRETS_SYNC_ALIGNMENT.md | 31 +++++++++++++++++++++++++++++++ docs/architecture.rst | 4 ++-- docs/vendor-fabric.rst | 6 ++++-- packages/agentic-fabric/README.md | 5 +++-- 6 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 SECRETS_SYNC_ALIGNMENT.md diff --git a/AGENTIC_REINFORCEMENT.md b/AGENTIC_REINFORCEMENT.md index f459450..3b5d1d5 100644 --- a/AGENTIC_REINFORCEMENT.md +++ b/AGENTIC_REINFORCEMENT.md @@ -35,6 +35,8 @@ This repository creates the runtime- and agent-aware superclass on top of - If something exists only because an AI framework wants a specific wrapper type, keep it here. - Do not push AI-framework shims back down into `vendor-fabric`. +- Do not bypass `vendor-fabric` to import SecretSync Go bindings or invoke the + Go runtime directly from this layer. ## Superclass Role diff --git a/AGENTS.md b/AGENTS.md index 9932a39..0469660 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,7 +50,9 @@ and remote state before finalizing this repo. `extended-data` owns base data primitives, logging, inputs, files, redaction, and generic workflows. `vendor-fabric` owns vendor connectors, provider-backed -sync, SecretSync for Python, and vendor-backed tools. +sync, the SecretSync Python facade and `VendorData` capability surface, and +provider dispatch. `agentic-fabric` owns agent-facing wrappers over those +capabilities. ## Architecture Docs diff --git a/SECRETS_SYNC_ALIGNMENT.md b/SECRETS_SYNC_ALIGNMENT.md new file mode 100644 index 0000000..2d3158f --- /dev/null +++ b/SECRETS_SYNC_ALIGNMENT.md @@ -0,0 +1,31 @@ +# SecretSync Alignment + +This file locks the intended SecretSync boundary for `agentic-fabric`. + +## Canonical Stack + +1. `secrets-sync` owns the Go runtime and gopy binding source. +2. `vendor-fabric` owns the Python facade, credential handoff, and + `VendorData`-level capability surface. +3. `agentic-fabric` owns runtime selection, tool wrapping, and orchestration. + +## This Repository's Role + +- Turn SecretSync-related vendor capabilities into framework-visible tools for + CrewAI, LangGraph, Strands, MCP, and related runtimes. +- Preserve the superclass chain `ExtendedData -> VendorData -> AgenticData`. +- Keep install guidance honest: vendor extras and agent runtime extras are + separate concerns unless an explicit passthrough extra is later added. + +## Required Direction + +- Access SecretSync through `VendorData` and `vendor-fabric` capabilities. +- Keep agent/session abstractions additive over the vendor layer. +- Let runners and tool registries decide how SecretSync becomes usable in a + given runtime without re-owning provider logic here. + +## Forbidden Drift + +- Do not import or bind directly to the SecretSync Go runtime from here. +- Do not own credential handoff, provider clients, or binding plumbing here. +- Do not document fake aggregate extras that do not actually exist. diff --git a/docs/architecture.rst b/docs/architecture.rst index 521cb4d..8016bdf 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -21,8 +21,8 @@ Boundary ``extended-data`` owns base data primitives, logging, input handling, files, redaction, and generic workflows. ``vendor-fabric`` owns vendor -API connectors, provider-backed sync, SecretSync for Python, provider -capability metadata, and provider dispatch. +API connectors, provider-backed sync, SecretSync Python facade/capability +surfaces, provider capability metadata, and provider dispatch. AgenticData ----------- diff --git a/docs/vendor-fabric.rst b/docs/vendor-fabric.rst index 863382a..7207b1a 100644 --- a/docs/vendor-fabric.rst +++ b/docs/vendor-fabric.rst @@ -6,8 +6,10 @@ until ``vendor-fabric`` is published and resolvable from PyPI. The core package still imports without vendor SDKs installed. The integration rule is simple: vendor IO, provider capability metadata, -and SecretSync behavior belong in ``vendor-fabric``; crew orchestration, -runtime selection, and agent-facing framework wrappers belong here. +and the SecretSync Python facade/capability surface belong in +``vendor-fabric``. The SecretSync Go runtime and gopy binding source +belong in ``secrets-sync``. Crew orchestration, runtime selection, and +agent-facing framework wrappers belong here. ``AgenticData`` subclasses ``VendorData`` when ``vendor-fabric`` is installed. Until then, it keeps runtime context importable and raises diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index c044aca..3786ac6 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -162,8 +162,9 @@ world-writable. - `extended-data` owns base data containers, logging, input handling, files, redaction, and generic workflows. -- `vendor-fabric` owns vendor connectors, provider-backed sync, SecretSync for - Python, provider capability metadata, and provider dispatch. +- `vendor-fabric` owns vendor connectors, provider-backed sync, the SecretSync + Python facade/capability surfaces, provider capability metadata, and provider + dispatch. - `agentic-fabric` owns crew discovery, runner selection, framework adapters, agent-facing tool wrappers, and orchestration. From 26453921ed4720733da94b6773b6362dda8b0f25 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 02:05:10 -0500 Subject: [PATCH 12/35] fix: harden local cli profile loading --- SECRETS_SYNC_ALIGNMENT.md | 7 ++ .../src/agentic_fabric/core/discovery.py | 8 +- .../runners/local_cli_runner.py | 73 ++++++++++++++++--- .../tests/test_discovery_nested.py | 11 +++ .../tests/test_single_agent_runners.py | 62 +++++++++++++++- 5 files changed, 144 insertions(+), 17 deletions(-) diff --git a/SECRETS_SYNC_ALIGNMENT.md b/SECRETS_SYNC_ALIGNMENT.md index 2d3158f..bead3d1 100644 --- a/SECRETS_SYNC_ALIGNMENT.md +++ b/SECRETS_SYNC_ALIGNMENT.md @@ -9,6 +9,13 @@ This file locks the intended SecretSync boundary for `agentic-fabric`. `VendorData`-level capability surface. 3. `agentic-fabric` owns runtime selection, tool wrapping, and orchestration. +## Binding Contract + +- PyPI distribution: `secrets-sync-python-binding` +- Python import/module: `secrets_sync` +- This repository should not import `secretssync` directly; it should consume + SecretSync through `vendor-fabric`. + ## This Repository's Role - Turn SecretSync-related vendor capabilities into framework-visible tools for diff --git a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py index 1b01175..e4466dc 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py @@ -93,14 +93,14 @@ def discover_packages( # Try each framework directory in priority order for dir_name in dirs_to_check: config_dir = pkg_dir / dir_name - if config_dir.exists() and (config_dir / "manifest.yaml").exists(): + if config_dir.is_dir() and (config_dir / "manifest.yaml").exists(): packages[pkg_dir.name] = config_dir break # Use first found (highest priority) # Also check workspace root for standalone projects for dir_name in dirs_to_check: config_dir = workspace_root / dir_name - if config_dir.exists() and (config_dir / "manifest.yaml").exists(): + if config_dir.is_dir() and (config_dir / "manifest.yaml").exists(): # Use the workspace name or a default pkg_name = workspace_root.name or "default" if pkg_name not in packages: @@ -140,7 +140,7 @@ def discover_all_framework_configs( pkg_configs: dict[str | None, Path] = {} for dir_name in FRAMEWORK_DIRS: config_dir = pkg_dir / dir_name - if config_dir.exists() and (config_dir / "manifest.yaml").exists(): + if config_dir.is_dir() and (config_dir / "manifest.yaml").exists(): framework = DIR_TO_FRAMEWORK[dir_name] pkg_configs[framework] = config_dir @@ -151,7 +151,7 @@ def discover_all_framework_configs( root_configs: dict[str | None, Path] = {} for dir_name in FRAMEWORK_DIRS: config_dir = workspace_root / dir_name - if config_dir.exists() and (config_dir / "manifest.yaml").exists(): + if config_dir.is_dir() and (config_dir / "manifest.yaml").exists(): framework = DIR_TO_FRAMEWORK[dir_name] root_configs[framework] = config_dir diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py index 622d461..ecbec41 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py @@ -21,6 +21,7 @@ from __future__ import annotations import os +import re import shlex import stat import subprocess @@ -78,6 +79,8 @@ class LocalCLIConfig: _CONFIG_FIELD_NAMES = {item.name for item in fields(LocalCLIConfig)} +_ENV_VAR_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") +_SHELL_CONTROL_TOKENS = (";", "&&", "||", "|", "`", "$(", "\n", "\r", "<", ">") def _copy_config(config: LocalCLIConfig) -> LocalCLIConfig: @@ -102,21 +105,36 @@ def _validate_profile_config(name: str, config_dict: dict[str, Any]) -> dict[str raise TypeError(msg) command_parts = shlex.split(command) - if not command_parts or any(part in {";", "&&", "||", "|"} for part in command_parts): + if not command_parts or any(_contains_shell_control(part) for part in command_parts): msg = f"Profile '{name}' command is not a direct executable invocation" raise ValueError(msg) + for part in command_parts: + _validate_cli_fragment(name, "command", part) + + if task_flag: + _validate_cli_fragment(name, "task_flag", task_flag) for field_name in ("auth_env", "additional_flags"): value = config_dict.get(field_name, []) if not isinstance(value, list) or not all(isinstance(item, str) and item for item in value): msg = f"Profile '{name}' field '{field_name}' must be a list of non-empty strings" raise ValueError(msg) + if field_name == "auth_env": + invalid_vars = [item for item in value if not _ENV_VAR_RE.match(item)] + if invalid_vars: + msg = f"Profile '{name}' field 'auth_env' contains invalid environment variable names: {invalid_vars}" + raise ValueError(msg) + else: + for item in value: + _validate_cli_fragment(name, field_name, item) for field_name in ("subcommand", "auto_approve", "structured_output", "model_flag", "default_model", "working_dir_flag"): value = config_dict.get(field_name) if value is not None and not isinstance(value, str): msg = f"Profile '{name}' field '{field_name}' must be a string when provided" raise ValueError(msg) + if value: + _validate_cli_fragment(name, field_name, value) timeout = config_dict.get("timeout", 300) if not isinstance(timeout, int) or timeout <= 0 or timeout > 3600: @@ -126,6 +144,27 @@ def _validate_profile_config(name: str, config_dict: dict[str, Any]) -> dict[str return config_dict +def _contains_shell_control(value: str) -> bool: + """Return whether a config string contains shell control syntax.""" + return any(token in value for token in _SHELL_CONTROL_TOKENS) + + +def _validate_cli_fragment(profile_name: str, field_name: str, value: str) -> None: + """Reject shell control syntax in config-controlled CLI fragments.""" + if _contains_shell_control(value): + msg = f"Profile '{profile_name}' field '{field_name}' contains shell control syntax" + raise ValueError(msg) + if not shlex.split(value): + msg = f"Profile '{profile_name}' field '{field_name}' must contain at least one CLI argument" + raise ValueError(msg) + + +def _extend_cli_fragment(cmd: list[str], value: str | None) -> None: + """Append a validated config fragment as one or more subprocess arguments.""" + if value: + cmd.extend(shlex.split(value)) + + def _validate_profiles_file_permissions(profiles_file: Path) -> None: """Reject local CLI profile files writable by group or other users.""" if os.name != "posix": @@ -218,8 +257,20 @@ def _load_profiles(cls) -> dict[str, LocalCLIConfig]: with open(profiles_file, encoding="utf-8") as f: data = yaml.safe_load(f) + if not isinstance(data, dict): + msg = f"Profiles file must contain a mapping: {profiles_file}" + raise TypeError(msg) + profiles = {} - for name, config_dict in data.get("profiles", {}).items(): + raw_profiles = data.get("profiles", {}) + if not isinstance(raw_profiles, dict): + msg = f"Profiles file 'profiles' key must contain a mapping: {profiles_file}" + raise TypeError(msg) + + for name, config_dict in raw_profiles.items(): + if not isinstance(name, str) or not isinstance(config_dict, dict): + msg = f"Profiles file has invalid profile entry: {name!r}" + raise TypeError(msg) profiles[name] = LocalCLIConfig(**_validate_profile_config(name, config_dict)) cls._profiles_cache = profiles @@ -354,13 +405,14 @@ def _build_command( # Add subcommand if present (e.g., "ollama run") if self.config.subcommand: - cmd.append(self.config.subcommand) + _extend_cli_fragment(cmd, self.config.subcommand) # Add model if specified and tool supports it # For positional model (like ollama), add before task if model: if self.config.model_flag: - cmd.extend([self.config.model_flag, model]) + _extend_cli_fragment(cmd, self.config.model_flag) + cmd.append(model) else: # Positional model (e.g., "ollama run ") cmd.append(model) @@ -370,24 +422,27 @@ def _build_command( # Add task if self.config.task_flag: - cmd.extend([self.config.task_flag, task]) + _extend_cli_fragment(cmd, self.config.task_flag) + cmd.append(task) else: # Positional argument cmd.append(task) # Add auto-approve flag if requested and supported if auto_approve and self.config.auto_approve: - cmd.append(self.config.auto_approve) + _extend_cli_fragment(cmd, self.config.auto_approve) # Add structured output flag if requested and supported if structured_output and self.config.structured_output: - cmd.append(self.config.structured_output) + _extend_cli_fragment(cmd, self.config.structured_output) # Add working directory if supported if working_dir and self.config.working_dir_flag: - cmd.extend([self.config.working_dir_flag, working_dir]) + _extend_cli_fragment(cmd, self.config.working_dir_flag) + cmd.append(working_dir) # Add any additional flags - cmd.extend(self.config.additional_flags) + for flag in self.config.additional_flags: + _extend_cli_fragment(cmd, flag) return cmd diff --git a/packages/agentic-fabric/tests/test_discovery_nested.py b/packages/agentic-fabric/tests/test_discovery_nested.py index 51b3575..a980889 100644 --- a/packages/agentic-fabric/tests/test_discovery_nested.py +++ b/packages/agentic-fabric/tests/test_discovery_nested.py @@ -110,6 +110,17 @@ def test_missing_manifest_skips_directory(self, tmp_path: Path) -> None: packages = discover_packages(workspace_root=tmp_path) assert packages == {} + def test_file_shaped_config_path_is_skipped(self, tmp_path: Path) -> None: + """A file named like a config directory should not be discovered.""" + pkg_dir = tmp_path / "packages" / "test_pkg" + pkg_dir.mkdir(parents=True) + (pkg_dir / ".crew").write_text("not a directory", encoding="utf-8") + (tmp_path / ".langgraph").write_text("not a directory", encoding="utf-8") + + packages = discover_packages(workspace_root=tmp_path) + + assert packages == {} + def test_multiple_packages_discovered(self, tmp_path: Path) -> None: """Multiple packages should all be discovered.""" packages_dir = tmp_path / "packages" diff --git a/packages/agentic-fabric/tests/test_single_agent_runners.py b/packages/agentic-fabric/tests/test_single_agent_runners.py index 958775f..9480aa3 100644 --- a/packages/agentic-fabric/tests/test_single_agent_runners.py +++ b/packages/agentic-fabric/tests/test_single_agent_runners.py @@ -232,11 +232,38 @@ def test_rejects_shell_operator_in_command(self): with pytest.raises(ValueError, match="direct executable"): LocalCLIRunner({"command": "my-tool && rm -rf /", "task_flag": "--prompt"}) + @pytest.mark.parametrize( + "field_name,value", + [ + ("task_flag", "--prompt; rm -rf /"), + ("subcommand", "run && rm -rf /"), + ("auto_approve", "--yes | sh"), + ("structured_output", "--json > out"), + ("model_flag", "--model $(touch bad)"), + ("default_model", "model;touch-bad"), + ("working_dir_flag", "--cwd\n--bad"), + ], + ) + def test_rejects_shell_control_in_config_cli_fields(self, field_name, value): + """Config-controlled CLI fragments should stay single safe arguments.""" + config = {"command": "my-tool", "task_flag": "--prompt", field_name: value} + + with pytest.raises(ValueError, match=field_name): + LocalCLIRunner(config) + def test_rejects_invalid_additional_flags(self): """List fields should contain only non-empty strings.""" with pytest.raises(ValueError, match="additional_flags"): LocalCLIRunner({"command": "my-tool", "task_flag": "--prompt", "additional_flags": ["--ok", ""]}) + with pytest.raises(ValueError, match="additional_flags"): + LocalCLIRunner({"command": "my-tool", "task_flag": "--prompt", "additional_flags": ["--ok", "--bad;"]}) + + def test_rejects_invalid_auth_env_names(self): + """Auth env names should be valid environment variable identifiers.""" + with pytest.raises(ValueError, match="auth_env"): + LocalCLIRunner({"command": "my-tool", "task_flag": "--prompt", "auth_env": ["OK", "BAD-NAME"]}) + def test_rejects_invalid_optional_scalar_field(self): """Optional string fields should reject non-string values.""" with pytest.raises(ValueError, match="working_dir_flag"): @@ -259,6 +286,32 @@ def test_load_profiles_reports_missing_profiles_file(self, tmp_path: Path): with pytest.raises(FileNotFoundError, match=r"Expected local_cli_profiles\.yaml"): LocalCLIRunner._load_profiles() + def test_load_profiles_rejects_non_mapping_yaml(self, tmp_path: Path): + """Profiles YAML must have a mapping root and mapping profiles section.""" + profiles_file = tmp_path / "local_cli_profiles.yaml" + profiles_file.write_text("- not\n- a mapping\n", encoding="utf-8") + with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: + mock_path.return_value.parent = tmp_path + mock_path.return_value.__truediv__.return_value = profiles_file + + LocalCLIRunner._profiles_cache = None + + with pytest.raises(TypeError, match="must contain a mapping"): + LocalCLIRunner._load_profiles() + + def test_load_profiles_rejects_non_mapping_profiles_section(self, tmp_path: Path): + """Profiles YAML must define profiles as a mapping.""" + profiles_file = tmp_path / "local_cli_profiles.yaml" + profiles_file.write_text("profiles:\n - not-a-mapping\n", encoding="utf-8") + with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: + mock_path.return_value.parent = tmp_path + mock_path.return_value.__truediv__.return_value = profiles_file + + LocalCLIRunner._profiles_cache = None + + with pytest.raises(TypeError, match="profiles"): + LocalCLIRunner._load_profiles() + def test_init_with_config_object(self): """Should initialize with a LocalCLIConfig object.""" config = LocalCLIConfig( @@ -367,7 +420,7 @@ def test_run_with_structured_output(self, mock_run: MagicMock): command="test-tool", task_flag="--task", auth_env=["TEST_API_KEY"], - structured_output="--json", + structured_output="--output-format json", ) runner = LocalCLIRunner(config) @@ -376,7 +429,7 @@ def test_run_with_structured_output(self, mock_run: MagicMock): runner.run("Test task", structured_output=True) call_args = mock_run.call_args - assert "--json" in call_args[0][0] + assert call_args[0][0] == ["test-tool", "--task", "Test task", "--output-format", "json"] @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) @patch("subprocess.run") @@ -407,7 +460,7 @@ def test_run_with_additional_flags(self, mock_run: MagicMock): command="test-tool", task_flag="--task", auth_env=["TEST_API_KEY"], - additional_flags=["--no-cache", "--verbose"], + additional_flags=["--no-cache", "--log-level debug"], ) runner = LocalCLIRunner(config) @@ -418,7 +471,8 @@ def test_run_with_additional_flags(self, mock_run: MagicMock): call_args = mock_run.call_args cmd = call_args[0][0] assert "--no-cache" in cmd - assert "--verbose" in cmd + assert "--log-level" in cmd + assert "debug" in cmd @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) @patch("subprocess.run") From d08afc8291932094342a405051bcba3e82af8158 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 02:12:31 -0500 Subject: [PATCH 13/35] fix: validate local cli configs consistently --- .../runners/local_cli_runner.py | 56 ++++++++++++++++--- .../tests/test_single_agent_runners.py | 34 +++++++++++ 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py index ecbec41..877d2de 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py @@ -26,7 +26,7 @@ import stat import subprocess -from dataclasses import dataclass, field, fields, replace +from dataclasses import asdict, dataclass, field, fields, replace from pathlib import Path from typing import Any @@ -88,6 +88,12 @@ def _copy_config(config: LocalCLIConfig) -> LocalCLIConfig: return replace(config, auth_env=list(config.auth_env), additional_flags=list(config.additional_flags)) +def _validated_config(name: str, config: LocalCLIConfig | dict[str, Any]) -> LocalCLIConfig: + """Return a copied LocalCLIConfig after applying profile validation.""" + config_dict = asdict(config) if isinstance(config, LocalCLIConfig) else dict(config) + return LocalCLIConfig(**_validate_profile_config(name, config_dict)) + + def _validate_profile_config(name: str, config_dict: dict[str, Any]) -> dict[str, Any]: """Validate a local CLI profile loaded from YAML.""" unknown_fields = set(config_dict) - _CONFIG_FIELD_NAMES @@ -104,7 +110,7 @@ def _validate_profile_config(name: str, config_dict: dict[str, Any]) -> dict[str msg = f"Profile '{name}' must define task_flag as a string" raise TypeError(msg) - command_parts = shlex.split(command) + command_parts = _split_cli_fragment(name, "command", command) if not command_parts or any(_contains_shell_control(part) for part in command_parts): msg = f"Profile '{name}' command is not a direct executable invocation" raise ValueError(msg) @@ -154,17 +160,36 @@ def _validate_cli_fragment(profile_name: str, field_name: str, value: str) -> No if _contains_shell_control(value): msg = f"Profile '{profile_name}' field '{field_name}' contains shell control syntax" raise ValueError(msg) - if not shlex.split(value): + if not _split_cli_fragment(profile_name, field_name, value): msg = f"Profile '{profile_name}' field '{field_name}' must contain at least one CLI argument" raise ValueError(msg) +def _split_cli_fragment(profile_name: str, field_name: str, value: str) -> list[str]: + """Split a config-controlled CLI fragment with profile-specific errors.""" + try: + return shlex.split(value) + except ValueError as exc: + msg = f"Profile '{profile_name}' field '{field_name}' is not valid shell-style syntax" + raise ValueError(msg) from exc + + def _extend_cli_fragment(cmd: list[str], value: str | None) -> None: """Append a validated config fragment as one or more subprocess arguments.""" if value: cmd.extend(shlex.split(value)) +def _validate_profiles_file_source(profiles_file: Path) -> None: + """Require the bundled profiles path to be a regular profile file.""" + if profiles_file.name != "local_cli_profiles.yaml": + msg = f"Unexpected profiles file name: {profiles_file}" + raise ValueError(msg) + if profiles_file.is_symlink() or not profiles_file.is_file(): + msg = f"Profiles file must be a regular file: {profiles_file}" + raise FileNotFoundError(msg) + + def _validate_profiles_file_permissions(profiles_file: Path) -> None: """Reject local CLI profile files writable by group or other users.""" if os.name != "posix": @@ -176,6 +201,18 @@ def _validate_profiles_file_permissions(profiles_file: Path) -> None: raise PermissionError(msg) +def _validate_subprocess_argument(field_name: str, value: str | None) -> None: + """Reject argument values that cannot be passed safely to subprocess.""" + if value is None: + return + if not isinstance(value, str): + msg = f"{field_name} must be a string" + raise TypeError(msg) + if "\x00" in value: + msg = f"{field_name} contains a NUL byte" + raise ValueError(msg) + + class LocalCLIRunner(SingleAgentRunner): """Universal runner for CLI-based coding agents. @@ -224,10 +261,10 @@ def __init__( self.config = _copy_config(profiles[profile]) elif isinstance(profile, dict): # Convert dict to LocalCLIConfig - self.config = LocalCLIConfig(**_validate_profile_config("custom", profile)) + self.config = _validated_config("custom", profile) elif isinstance(profile, LocalCLIConfig): # Use provided LocalCLIConfig directly - self.config = profile + self.config = _validated_config("custom", profile) else: msg = "profile must be a profile name, LocalCLIConfig, or config dict" raise TypeError(msg) @@ -247,10 +284,11 @@ def _load_profiles(cls) -> dict[str, LocalCLIConfig]: # Find the profiles file profiles_file = Path(__file__).parent / "local_cli_profiles.yaml" - if not profiles_file.exists(): + if not profiles_file.is_file(): raise FileNotFoundError( f"Profiles file not found: {profiles_file}\nExpected local_cli_profiles.yaml in runners directory." ) + _validate_profiles_file_source(profiles_file) _validate_profiles_file_permissions(profiles_file) # Load and parse YAML @@ -294,7 +332,7 @@ def is_available(self) -> bool: from shutil import which # Check if command is in PATH - command = self.config.command.split()[0] + command = shlex.split(self.config.command)[0] return which(command) is not None def get_required_env_vars(self) -> list[str]: @@ -331,6 +369,10 @@ def run( RuntimeError: If tool execution fails or required env vars missing. subprocess.TimeoutExpired: If execution exceeds timeout. """ + _validate_subprocess_argument("task", task) + _validate_subprocess_argument("working_dir", working_dir) + _validate_subprocess_argument("model", model) + # Accept but don't use kwargs (reserved for future extensibility) _ = kwargs diff --git a/packages/agentic-fabric/tests/test_single_agent_runners.py b/packages/agentic-fabric/tests/test_single_agent_runners.py index 9480aa3..9cff69e 100644 --- a/packages/agentic-fabric/tests/test_single_agent_runners.py +++ b/packages/agentic-fabric/tests/test_single_agent_runners.py @@ -312,6 +312,22 @@ def test_load_profiles_rejects_non_mapping_profiles_section(self, tmp_path: Path with pytest.raises(TypeError, match="profiles"): LocalCLIRunner._load_profiles() + def test_load_profiles_rejects_symlink_profiles_file(self, tmp_path: Path): + """Bundled profiles should be read from a regular file.""" + target_file = tmp_path / "target.yaml" + target_file.write_text("profiles: {}\n", encoding="utf-8") + profiles_file = tmp_path / "local_cli_profiles.yaml" + profiles_file.symlink_to(target_file) + + with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: + mock_path.return_value.parent = tmp_path + mock_path.return_value.__truediv__.return_value = profiles_file + + LocalCLIRunner._profiles_cache = None + + with pytest.raises(FileNotFoundError, match="regular file"): + LocalCLIRunner._load_profiles() + def test_init_with_config_object(self): """Should initialize with a LocalCLIConfig object.""" config = LocalCLIConfig( @@ -324,6 +340,16 @@ def test_init_with_config_object(self): assert runner.config.command == "custom-tool" assert runner.config.task_flag == "--task" + def test_init_with_config_object_uses_profile_validation(self): + """LocalCLIConfig objects should not bypass fragment validation.""" + config = LocalCLIConfig( + command="custom-tool", + task_flag="--task; rm -rf /", + ) + + with pytest.raises(ValueError, match="task_flag"): + LocalCLIRunner(config) + def test_init_rejects_unsupported_profile_input(self): """Unsupported profile inputs should fail with a clear type error.""" with pytest.raises(TypeError, match="profile must be"): @@ -366,6 +392,14 @@ def test_run_missing_env_vars(self): with pytest.raises(RuntimeError, match="Missing required environment variables"): runner.run("test task") + def test_run_rejects_nul_task_argument(self): + """User arguments should be valid subprocess arguments.""" + config = LocalCLIConfig(command="test-tool", task_flag="--task") + runner = LocalCLIRunner(config) + + with pytest.raises(ValueError, match="NUL byte"): + runner.run("safe\x00unsafe") + @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) @patch("subprocess.run") def test_run_success(self, mock_run: MagicMock): From bbcf892ff678e0304692b1773562b60f49b0ab18 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 02:18:50 -0500 Subject: [PATCH 14/35] fix: externalize vulnerable crewai dependency path --- .github/dependabot.yml | 5 - docs/agentic-workflows.rst | 5 +- docs/getting-started.rst | 30 +- docs/index.rst | 8 +- packages/agentic-fabric/README.md | 12 +- packages/agentic-fabric/pyproject.toml | 4 +- .../src/agentic_fabric/runners/registry.py | 8 +- .../src/agentic_fabric/tools/registry.py | 2 +- .../agentic_fabric/tools/scraping_tools.py | 36 +- packages/agentic-fabric/tests/e2e/README.md | 3 +- .../agentic-fabric/tests/test_agentic_data.py | 4 +- .../tests/test_decomposer_edge_cases.py | 12 +- packages/agentic-fabric/tests/test_runners.py | 2 +- .../tests/test_scraping_tool_crawler.py | 6 - uv.lock | 2507 +---------------- 15 files changed, 75 insertions(+), 2569 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c6e3433..ac1ce4a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,11 +8,6 @@ updates: minor-and-patch: patterns: ["*"] update-types: ["minor", "patch"] - ignore: - # ChromaDB is pulled only through optional CrewAI extras. As of 2026-06-27, - # the latest published ChromaDB release is still in the vulnerable range, - # so Dependabot cannot produce a patched update yet. - - dependency-name: chromadb - package-ecosystem: github-actions directory: / schedule: diff --git a/docs/agentic-workflows.rst b/docs/agentic-workflows.rst index 5afc8c8..0ffaea1 100644 --- a/docs/agentic-workflows.rst +++ b/docs/agentic-workflows.rst @@ -53,7 +53,10 @@ framework: for runtime in get_framework_info(): print(runtime["name"], runtime["available"], runtime["install"]) -Supported runtime extras are ``crewai``, ``langgraph``, and ``strands``. +Supported runtime extras are ``langgraph`` and ``strands``. The CrewAI adapter +is lazy and can use an externally installed CrewAI runtime, but +``agentic-fabric`` does not publish a CrewAI extra while CrewAI's ChromaDB +dependency path has an upstream critical advisory with no patched version. Install the runtime you actually execute; the package does not publish an aggregate AI or all-frameworks extra. ``mcp`` and ``scraping`` remain focused optional tool surfaces. Local CLI runners are configured profiles over external diff --git a/docs/getting-started.rst b/docs/getting-started.rst index e183415..930c5b8 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -12,18 +12,16 @@ Install the runtime extras you actually use: .. code:: bash - pip install "agentic-fabric[crewai]" pip install "agentic-fabric[langgraph]" pip install "agentic-fabric[strands]" pip install "agentic-fabric[mcp]" pip install "agentic-fabric[scraping]" -``mcp`` exposes MCP integration dependencies. ``scraping`` installs the CrewAI -tools surface plus ``requests`` and ``beautifulsoup4``. Vendor passthrough -extras are deferred until ``vendor-fabric`` is published with a stable -optional-extra contract. There is no aggregate AI or all-frameworks extra. -Test, typing, docs, and dev extras are reserved for repository validation and -package maintenance. +``mcp`` exposes MCP integration dependencies. ``scraping`` installs the +first-party requests/BeautifulSoup scraping helpers. Vendor passthrough extras +are deferred until ``vendor-fabric`` is published with a stable optional-extra +contract. There is no aggregate AI or all-frameworks extra. Test, typing, docs, +and dev extras are reserved for repository validation and package maintenance. Local CLI runners do not require a Python extra. They shell out to external executables that you install separately: @@ -47,9 +45,9 @@ framework-agnostic. A framework-specific directory requires that runtime. If a required runtime is unavailable, runtime errors include the matching -``agentic-fabric[...]`` install command. Automatic selection follows CrewAI, -then LangGraph, then Strands, but explicit runtime selection always wins unless -the crew is stored in a framework-specific directory. +install guidance. Automatic selection follows CrewAI, then LangGraph, then +Strands, but explicit runtime selection always wins unless the crew is stored +in a framework-specific directory. Use ``AgenticData`` when runtime context should travel with data and registered crew definitions: @@ -65,9 +63,9 @@ registered crew definitions: CrewAI Dependency Note ---------------------- -The ``crewai`` and ``scraping`` extras include dependencies chosen by CrewAI. -If a transitive CrewAI dependency has an upstream advisory with no patched -release, upgrading ``agentic-fabric`` or relaxing its CrewAI range cannot clear -that advisory by itself. Core and local CLI installs do not include CrewAI. -Track the upstream CrewAI and dependency advisory state before enabling those -optional extras in sensitive environments. +The CrewAI adapter remains lazy and source-compatible, but +``agentic-fabric`` does not publish a CrewAI extra while current CrewAI +releases depend on ChromaDB releases covered by an upstream critical advisory +with no patched version. Install CrewAI separately only after reviewing that +advisory state. Core, local CLI, LangGraph, Strands, MCP, and first-party +scraping installs do not include CrewAI. diff --git a/docs/index.rst b/docs/index.rst index 2efc753..d77732b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,16 +9,16 @@ LangGraph, Strands, or local CLI runners. .. code:: bash pip install agentic-fabric - pip install "agentic-fabric[crewai]" pip install "agentic-fabric[langgraph]" pip install "agentic-fabric[strands]" pip install "agentic-fabric[mcp]" pip install "agentic-fabric[scraping]" Local CLI runners are part of the core install because they shell out to -external executables and require no third-party Python framework. CrewAI, -LangGraph, Strands, MCP, scraping helpers, and vendor providers are opt-in -extras. +external executables and require no third-party Python framework. LangGraph, +Strands, MCP, scraping helpers, and vendor providers are opt-in extras. CrewAI +support stays lazy but CrewAI itself is an external install while its ChromaDB +dependency path has an upstream critical advisory with no patched version. Vendor-backed passthrough extras are deferred until ``vendor-fabric`` is published with a stable optional-extra contract. Until then, vendor references diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index 3786ac6..8b48391 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -19,7 +19,6 @@ from what is installed. pip install agentic-fabric # With a specific framework -pip install "agentic-fabric[crewai]" pip install "agentic-fabric[langgraph]" pip install "agentic-fabric[strands]" @@ -37,10 +36,11 @@ Vendor-backed passthrough extras will be added after the upstream vendor references stay lazy and report install guidance at use time. There is no aggregate AI extra. Install exactly the framework or provider path -you use. The `crewai` and `scraping` extras include dependencies selected by -CrewAI. If a transitive CrewAI dependency has an upstream advisory with no -patched release, upgrading `agentic-fabric` alone cannot clear that advisory; -core/local-CLI installs are unaffected. +you use. The CrewAI adapter remains lazy, but `agentic-fabric` does not publish +a CrewAI extra while CrewAI depends on ChromaDB releases covered by an upstream +critical advisory with no patched version. Install CrewAI separately only after +reviewing that advisory state. Core, local-CLI, and first-party scraping +installs are unaffected. ## Quick Start @@ -112,7 +112,7 @@ result = session.run_agent("reviewer", runtime="crewai") - Framework agnostic: one crew definition, multiple runtime backends. - Lazy imports: core package import does not require CrewAI, LangGraph, Strands, or vendor SDKs. -- Focused extras: `crewai`, `langgraph`, `strands`, `mcp`, `scraping`, +- Focused extras: `langgraph`, `strands`, `mcp`, `scraping`, `tests`, `typing`, `docs`, and `dev`. - `AgenticData`: carries data, registered crews, active runtime selection, and vendor-layer context together. diff --git a/packages/agentic-fabric/pyproject.toml b/packages/agentic-fabric/pyproject.toml index ac64c28..beb8dfc 100644 --- a/packages/agentic-fabric/pyproject.toml +++ b/packages/agentic-fabric/pyproject.toml @@ -29,11 +29,10 @@ dependencies = [ ] [project.optional-dependencies] -crewai = ["crewai[anthropic]>=1.5.0"] langgraph = ["langgraph>=0.2.0", "langchain-anthropic>=0.3.0"] strands = ["strands-agents>=0.1.0"] mcp = ["mcp>=1.0.0"] -scraping = ["beautifulsoup4>=4.12.0", "crewai-tools>=1.5.0", "requests>=2.31.0"] +scraping = ["beautifulsoup4>=4.12.0", "requests>=2.31.0"] tests = [ "coverage[toml]>=7.6.0", "pytest-asyncio>=0.23.0", @@ -173,7 +172,6 @@ module = [ "bs4", "crewai", "crewai.*", - "crewai_tools", "langchain_anthropic", "langgraph", "langgraph.*", diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/registry.py b/packages/agentic-fabric/src/agentic_fabric/runners/registry.py index a8b031e..1a1ccbb 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/registry.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/registry.py @@ -18,7 +18,7 @@ class RuntimeSpec: runner_module: str runner_class: str install: str - extra: str + extra: str | None description: str = "" def as_dict(self, *, available: bool) -> dict[str, Any]: @@ -136,9 +136,9 @@ def install_command(runtime: str) -> str: import_name="crewai", runner_module="agentic_fabric.runners.crewai_runner", runner_class="CrewAIRunner", - install='pip install "agentic-fabric[crewai]"', - extra="crewai", - description="Full multi-agent crew orchestration with CrewAI.", + install="pip install crewai", + extra=None, + description="CrewAI orchestration through an externally installed CrewAI runtime.", ) ) register_runtime( diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/registry.py b/packages/agentic-fabric/src/agentic_fabric/tools/registry.py index 8a702b6..e4e4582 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/registry.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/registry.py @@ -34,7 +34,7 @@ def factory() -> Any: "GameCodeWriterTool": _build_factory("agentic_fabric.tools.file_tools", "GameCodeWriterTool"), "GameCodeReaderTool": _build_factory("agentic_fabric.tools.file_tools", "GameCodeReaderTool"), "DirectoryListTool": _build_factory("agentic_fabric.tools.file_tools", "DirectoryListTool"), - "ScrapeWebsiteTool": _build_factory("crewai_tools", "ScrapeWebsiteTool"), + "ScrapeWebsiteTool": _build_factory("agentic_fabric.tools.scraping_tools", "ScrapeWebsiteTool"), "CrawlWebsiteTool": _build_factory("agentic_fabric.tools.scraping_tools", "CrawlWebsiteTool"), } diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py b/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py index d69d6b1..bf5c9ca 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py @@ -11,7 +11,6 @@ import requests from bs4 import BeautifulSoup -from crewai_tools import ScrapeWebsiteTool log = logging.getLogger(__name__) @@ -47,6 +46,35 @@ def _is_safe_crawl_url(url: str) -> bool: ) +class ScrapeWebsiteTool: + """A small first-party website scraping tool with CrewAI-compatible shape.""" + + name: str = "ScrapeWebsiteTool" + description: str = "Scrape readable content from a URL." + + def _run(self, url: str) -> str: + """Scrape readable content from one safe HTTP(S) URL.""" + if not _is_safe_crawl_url(url): + log.warning("Skipping unsafe scrape URL %s", url) + return "" + + try: + response = requests.get(url, timeout=30) + response.raise_for_status() + except requests.RequestException: + log.exception("Error scraping %s", url) + return "" + + soup = BeautifulSoup(response.content, "html.parser") + return self._scrape_content(soup) + + def _scrape_content(self, soup: BeautifulSoup) -> str: + """Scrape readable content from a BeautifulSoup object.""" + for script_or_style in soup(["script", "style"]): + script_or_style.decompose() + return " ".join(t.strip() for t in soup.stripped_strings) + + class CrawlWebsiteTool(ScrapeWebsiteTool): """A tool for crawling a website and scraping its content. @@ -111,9 +139,3 @@ def _run(self, url: str) -> str: log.exception("Error crawling %s", current_url) return " ".join(scraped_content) - - def _scrape_content(self, soup: BeautifulSoup) -> str: - """Scrapes the readable content from a BeautifulSoup object.""" - for script_or_style in soup(["script", "style"]): - script_or_style.decompose() - return " ".join(t.strip() for t in soup.stripped_strings) diff --git a/packages/agentic-fabric/tests/e2e/README.md b/packages/agentic-fabric/tests/e2e/README.md index 8b7ccfd..1da12d0 100644 --- a/packages/agentic-fabric/tests/e2e/README.md +++ b/packages/agentic-fabric/tests/e2e/README.md @@ -143,7 +143,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: hynek/setup-cached-uv@v2 - - run: uv sync --extra tests --extra crewai --extra langgraph --extra strands + - run: uv sync --extra tests --extra langgraph --extra strands + - run: uv pip install crewai - run: uv run pytest tests/e2e/ --e2e -v env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/packages/agentic-fabric/tests/test_agentic_data.py b/packages/agentic-fabric/tests/test_agentic_data.py index 7a1e495..c650e9c 100644 --- a/packages/agentic-fabric/tests/test_agentic_data.py +++ b/packages/agentic-fabric/tests/test_agentic_data.py @@ -31,7 +31,7 @@ def test_use_runtime_strict_reports_install_guidance(monkeypatch: pytest.MonkeyP """Unavailable runtimes should raise a typed error with install guidance.""" monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: False) - with pytest.raises(RuntimeUnavailableError, match="agentic-fabric\\[crewai\\]"): + with pytest.raises(RuntimeUnavailableError, match="pip install crewai"): AgenticData().use_runtime("crewai") @@ -206,7 +206,7 @@ def test_select_runtime_reports_unavailable_requested_runtime(monkeypatch: pytes """Unavailable explicit runtimes should raise install guidance.""" monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: False) - with pytest.raises(RuntimeUnavailableError, match="agentic-fabric\\[crewai\\]"): + with pytest.raises(RuntimeUnavailableError, match="pip install crewai"): AgenticData().select_runtime("crewai") diff --git a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py index 986490b..27938ee 100644 --- a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py +++ b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py @@ -126,7 +126,7 @@ def test_error_message_lists_install_options(self) -> None: """RuntimeError should include installation instructions.""" with ( patch("agentic_fabric.core.decomposer.is_framework_available", return_value=False), - pytest.raises(RuntimeError, match=r"agentic-fabric\[crewai\]"), + pytest.raises(RuntimeError, match="pip install crewai"), ): detect_framework() @@ -242,7 +242,7 @@ def test_decompose_crew_reports_conflicting_or_missing_required_runtime( monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda framework: False) - with pytest.raises(RuntimeError, match=r"agentic-fabric\[crewai\]"): + with pytest.raises(RuntimeError, match="pip install crewai"): decompose_crew(crew_config) def test_run_crew_auto_enforces_required_runtime_and_runs( @@ -296,16 +296,16 @@ def test_framework_info_includes_install_guidance(self) -> None: assert isinstance(info, dict) assert info["name"] == "crewai" - assert info["extra"] == "crewai" + assert info["extra"] is None assert info["available"] is False - assert "agentic-fabric[crewai]" in info["install"] + assert info["install"] == "pip install crewai" class TestGetInstallCommand: """Tests for _get_install_command.""" - def test_crewai_includes_tools(self) -> None: - assert _get_install_command("crewai") == 'pip install "agentic-fabric[crewai]"' + def test_crewai_is_external_install(self) -> None: + assert _get_install_command("crewai") == "pip install crewai" def test_langgraph_includes_langchain(self) -> None: assert _get_install_command("langgraph") == 'pip install "agentic-fabric[langgraph]"' diff --git a/packages/agentic-fabric/tests/test_runners.py b/packages/agentic-fabric/tests/test_runners.py index 3baaec8..e65ea0b 100644 --- a/packages/agentic-fabric/tests/test_runners.py +++ b/packages/agentic-fabric/tests/test_runners.py @@ -40,7 +40,7 @@ def test_init_reports_missing_crewai(self, crew_mocker: CrewMocker) -> None: try: CrewAIRunner() except RuntimeError as exc: - assert 'pip install "agentic-fabric[crewai]"' in str(exc) + assert "pip install crewai" in str(exc) else: # pragma: no cover - defensive if CrewAI import mocking stops working raise AssertionError("CrewAIRunner did not report missing CrewAI") diff --git a/packages/agentic-fabric/tests/test_scraping_tool_crawler.py b/packages/agentic-fabric/tests/test_scraping_tool_crawler.py index 77201f2..0d0b09b 100644 --- a/packages/agentic-fabric/tests/test_scraping_tool_crawler.py +++ b/packages/agentic-fabric/tests/test_scraping_tool_crawler.py @@ -14,13 +14,9 @@ def import_scraping_tools(monkeypatch: pytest.MonkeyPatch) -> types.ModuleType: """Import scraping_tools with lightweight optional dependency stand-ins.""" - fake_crewai_tools = types.ModuleType("crewai_tools") fake_requests = types.ModuleType("requests") fake_bs4 = types.ModuleType("bs4") - class ScrapeWebsiteTool: - """Minimal stand-in for crewai_tools.ScrapeWebsiteTool.""" - class RequestError(Exception): """Minimal stand-in for requests.RequestException.""" @@ -45,12 +41,10 @@ def find_all(self, tag: str, href: bool = False) -> list[FakeLink]: def stripped_strings(self) -> list[str]: return [part.strip() for part in re.sub(r"<[^>]+>", " ", self.content).split() if part.strip()] - fake_crewai_tools.ScrapeWebsiteTool = ScrapeWebsiteTool fake_requests.RequestException = RequestError fake_requests.get = MagicMock() fake_bs4.BeautifulSoup = BeautifulSoup - monkeypatch.setitem(sys.modules, "crewai_tools", fake_crewai_tools) monkeypatch.setitem(sys.modules, "requests", fake_requests) monkeypatch.setitem(sys.modules, "bs4", fake_bs4) sys.modules.pop("agentic_fabric.tools.scraping_tools", None) diff --git a/uv.lock b/uv.lock index 4477b10..ace7f69 100644 --- a/uv.lock +++ b/uv.lock @@ -43,9 +43,6 @@ dependencies = [ ] [package.optional-dependencies] -crewai = [ - { name = "crewai", extra = ["anthropic"] }, -] dev = [ { name = "coverage", extra = ["toml"] }, { name = "mypy" }, @@ -75,7 +72,6 @@ mcp = [ ] scraping = [ { name = "beautifulsoup4" }, - { name = "crewai-tools" }, { name = "requests" }, ] strands = [ @@ -101,8 +97,6 @@ requires-dist = [ { name = "agentic-fabric", extras = ["tests", "typing"], marker = "extra == 'dev'", editable = "packages/agentic-fabric" }, { name = "beautifulsoup4", marker = "extra == 'scraping'", specifier = ">=4.12.0" }, { name = "coverage", extras = ["toml"], marker = "extra == 'tests'", specifier = ">=7.6.0" }, - { name = "crewai", extras = ["anthropic"], marker = "extra == 'crewai'", specifier = ">=1.5.0" }, - { name = "crewai-tools", marker = "extra == 'scraping'", specifier = ">=1.5.0" }, { name = "furo", marker = "extra == 'docs'", specifier = ">=2025.12.19" }, { name = "langchain-anthropic", marker = "extra == 'langgraph'", specifier = ">=0.3.0" }, { name = "langgraph", marker = "extra == 'langgraph'", specifier = ">=0.2.0" }, @@ -126,7 +120,7 @@ requires-dist = [ { name = "types-pyyaml", marker = "extra == 'typing'", specifier = ">=6.0.12.20240724" }, { name = "types-requests", marker = "extra == 'typing'", specifier = ">=2.33.0.20260408" }, ] -provides-extras = ["crewai", "langgraph", "strands", "mcp", "scraping", "tests", "typing", "dev", "docs"] +provides-extras = ["langgraph", "strands", "mcp", "scraping", "tests", "typing", "dev", "docs"] [[package]] name = "agentic-fabric-workspace" @@ -153,146 +147,6 @@ dev = [ { name = "tox-uv", specifier = ">=1.11" }, ] -[[package]] -name = "aiohappyeyeballs" -version = "2.6.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, -] - -[[package]] -name = "aiohttp" -version = "3.14.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, - { name = "yarl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/82/78/8ea7308cac6934de8c74a14f3d5f65d1c89287426688be79538d0e5c013d/aiohttp-3.14.1.tar.gz", hash = "sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035", size = 7955794, upload-time = "2026-06-07T21:09:35.529Z" } -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/8f/e1/a2872aa55495a70f61310d411541c6ee23812d9a884e000c716e1bc3edbf/aiohttp-3.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c1af67559445498b502030c35c59db59966f47041ca9de5b4e707f86bd10b5f", size = 518743, upload-time = "2026-06-07T21:06:09.749Z" }, - { 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/b5/63/a251a9d2a6cb45065b2ddc0bde2b3dd10108740a9a42f632c66405a761a2/aiohttp-3.14.1-cp311-cp311-win32.whl", hash = "sha256:313701e488100074ce99850404ee36e741abf6330179fec908a1944ecf570126", size = 458386, upload-time = "2026-06-07T21:06:35.279Z" }, - { url = "https://files.pythonhosted.org/packages/17/ca/69274c51dcd6e8947d77b2806cf47a4a15f2c846e2cbeb1882547d3da283/aiohttp-3.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:03ab4530fdcb3a543a122ba4b65ac9919da9fe9f78a03d328a6e38ff962f7aa5", size = 483406, upload-time = "2026-06-07T21:06:36.824Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8a/c25904f77690c3688ec140f87591ef11a0cfe36bf3d5c0f1f38056fb62b3/aiohttp-3.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:486f7d16ed54c39c2cbd7ca71fd8ba2b8bb7860df65bd7b6ed640bab96a38a8b", size = 452987, upload-time = "2026-06-07T21:06:38.371Z" }, - { 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/c2/82/280619e0bd7bf2454987e19282616e84762255dd9c8468f62382e8c191f1/aiohttp-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d", size = 512310, upload-time = "2026-06-07T21:06:42.207Z" }, - { 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/3e/61/a0c0a8f327a9c52095cdd8e312391b00d3ed64ab6c72bb5c33d8ec251cf7/aiohttp-3.14.1-cp312-cp312-win32.whl", hash = "sha256:ec8dc383ee57ea3e883477dcca3f11b65d58199f1080acaf4cd6ad9a99698be4", size = 452771, upload-time = "2026-06-07T21:07:09.669Z" }, - { url = "https://files.pythonhosted.org/packages/df/d9/ea367c75f16ac9c6cdc8febb25e8318fa21a2b1bc8d6514d4b2d890bface/aiohttp-3.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2aa92c87868cd13674989f9ee83e5f9f7ea4237589b728048e1f0c8f6caa3271", size = 479873, upload-time = "2026-06-07T21:07:11.538Z" }, - { url = "https://files.pythonhosted.org/packages/03/64/8d96784a7851156db8a4c6c3f6f91042fdf39fb15a4cc38c8b3c14833c45/aiohttp-3.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:2c840c90759922cb5e6dda94596e079a30fb5a5ba548e7e0dc00574703940847", size = 448073, upload-time = "2026-06-07T21:07:13.637Z" }, - { url = "https://files.pythonhosted.org/packages/bc/97/bd137012dd97e1649162b099135a80e1fd59aaa807b2430fc448d1029aff/aiohttp-3.14.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:b3a03285a7f9c7b016324574a6d92a1c895da6b978cb8f1deee3ac72bc6da178", size = 506882, upload-time = "2026-06-07T21:07:15.501Z" }, - { url = "https://files.pythonhosted.org/packages/ef/79/e5cc690e9d922a66887ceeaca53a8ffd5a7b0be3816142b7abc433742d89/aiohttp-3.14.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:2a73f487ab8ef5abbb24b7aa9b73e98eaba9e9e031804ff2416f02eca315ccaf", size = 515270, upload-time = "2026-06-07T21:07:17.53Z" }, - { 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" }, - { url = "https://files.pythonhosted.org/packages/78/c0/5ebe5270a7c140d7c6f79dcb018640225f14d406c149e4eec04a7d82fe71/aiohttp-3.14.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1b9748363260121d2927704f5d4fc498150669ca3ae93625986ee89c8f80dcd4", size = 501564, upload-time = "2026-06-07T21:07:23.388Z" }, - { 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/b2/f4/c4227aacfacc5cb0cc2d119b65301d177912a6842cd64e120c47af76064f/aiohttp-3.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4dfd6e47d3c44c2279907607f73a4240b88c69eb8b90da7e2441a8045dfd21da", size = 510918, upload-time = "2026-06-07T21:07:27.28Z" }, - { 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" }, - { url = "https://files.pythonhosted.org/packages/e4/ae/dbce10533d3896d544d5053939ed75b7dc31a1b0973d959b1b5ae21028d6/aiohttp-3.14.1-cp313-cp313-win32.whl", hash = "sha256:e509a55f681e6158c20f70f102f9cf61fb20fbc382272bc6d94b7343f2582780", size = 452662, upload-time = "2026-06-07T21:07:56.06Z" }, - { url = "https://files.pythonhosted.org/packages/7b/d9/0bf1a19362c32f06229da5e7ddfcec91f93474d6307f7a2d3135e9c674dc/aiohttp-3.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:1ac8531b638959718e18c2207fbfe297819875da46a740b29dfa29beba64355a", size = 479748, upload-time = "2026-06-07T21:07:58.319Z" }, - { url = "https://files.pythonhosted.org/packages/22/0a/62e7232dc9484fbec112ceb32efb6a624cc7994ec6e2b019286f17c4e8f2/aiohttp-3.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:250d14af67f6b6a1a4a811049b1afa69d61d617fca6bf33149b3ab1a6dbcf7b8", size = 447723, upload-time = "2026-06-07T21:08:00.154Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a1/5fafa04e1ca91ddb47608699d60649c1c6db3cf41c99e78fc4056f9513db/aiohttp-3.14.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15", size = 508531, upload-time = "2026-06-07T21:08:02.093Z" }, - { url = "https://files.pythonhosted.org/packages/fa/2e/bfa02f699d87ffc86d5959270b28f1cb410add3ccaced8ed2e0b8a5238fc/aiohttp-3.14.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8", size = 514718, upload-time = "2026-06-07T21:08:04.476Z" }, - { url = "https://files.pythonhosted.org/packages/85/a5/9594ad6289eebbc97d167c44213d557807f90e59115caad24de21ad2c3b1/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3", size = 487918, upload-time = "2026-06-07T21:08:06.377Z" }, - { url = "https://files.pythonhosted.org/packages/b4/61/16a32c36c3c49edec122a3dc811f2057df2f94d3b14aa107c8017d981618/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba", size = 494014, upload-time = "2026-06-07T21:08:08.263Z" }, - { url = "https://files.pythonhosted.org/packages/9b/89/3ebcf96ed99c05bec9c434aaac6963fd3cbab4a786ae739908a144d9ce44/aiohttp-3.14.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397", size = 502398, upload-time = "2026-06-07T21:08:10.244Z" }, - { url = "https://files.pythonhosted.org/packages/fd/3d/b74870a0c2d40c355928cd5b96c7a11fa821b8a40fc41365e64479b151fb/aiohttp-3.14.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448", size = 758018, upload-time = "2026-06-07T21:08:12.447Z" }, - { url = "https://files.pythonhosted.org/packages/d3/66/f42f5c984d99e49c6cff5f26f590750f2e2f7ef1fcfb99966ab5be1b632e/aiohttp-3.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004", size = 512462, upload-time = "2026-06-07T21:08:14.624Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a7/248e1aebe0c7810b0271e021a0f2a5eb6e78a051885b3c9df49f42a5802d/aiohttp-3.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983", size = 512824, upload-time = "2026-06-07T21:08:16.572Z" }, - { url = "https://files.pythonhosted.org/packages/26/97/2aa0e5ba0727dc3bd5aaebb7ccbc510f7dfb7fb961ec87497cd496635ab1/aiohttp-3.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe", size = 1749898, upload-time = "2026-06-07T21:08:18.635Z" }, - { url = "https://files.pythonhosted.org/packages/00/8d/e97f6c96c891d457c8479d92a514ba194d0412f981d72c70341ee18488ed/aiohttp-3.14.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333", size = 1710114, upload-time = "2026-06-07T21:08:20.892Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e6/aa8d7e863048c8fceb5cd6ce74017311cec3ead07847387e12265fb4444e/aiohttp-3.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0", size = 1802541, upload-time = "2026-06-07T21:08:23.044Z" }, - { url = "https://files.pythonhosted.org/packages/83/a8/72193137de57fda4ebfae4563182d082c8856e3b6e9871d0b46f028fb369/aiohttp-3.14.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a", size = 1875776, upload-time = "2026-06-07T21:08:25.288Z" }, - { url = "https://files.pythonhosted.org/packages/a0/18/938441025db6769a3464596b2410af3afde0b21eb2f204c6f766f68af4bd/aiohttp-3.14.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602", size = 1760329, upload-time = "2026-06-07T21:08:27.363Z" }, - { url = "https://files.pythonhosted.org/packages/60/29/bf2496b4065e76e09fe48015aaffe5ce161d8f089b06ac6982070f653076/aiohttp-3.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca", size = 1587293, upload-time = "2026-06-07T21:08:29.805Z" }, - { url = "https://files.pythonhosted.org/packages/49/a2/2136674d52123b1354bd05dd5753c318db47dc0c927cc70b27bab3755456/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35", size = 1714756, upload-time = "2026-06-07T21:08:32.094Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b9/e5fd2e6f915503081c0f9b1e8540947037929c70c191da2e4d54b31a21a1/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844", size = 1721052, upload-time = "2026-06-07T21:08:34.167Z" }, - { url = "https://files.pythonhosted.org/packages/63/5a/2833e324a2263e104e31e2e91bc5bbee81bc499afd32203faee048a883f0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496", size = 1766888, upload-time = "2026-06-07T21:08:36.95Z" }, - { url = "https://files.pythonhosted.org/packages/57/fa/dea6511870913162f3b2e8c42a7614eb203a4540b8c2da43e0bfb0548f3c/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5", size = 1581679, upload-time = "2026-06-07T21:08:39.292Z" }, - { url = "https://files.pythonhosted.org/packages/14/bd/3cf0d55e71784b33534e9710a67d382d900598b4787fbce6cc7317f8c42a/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95", size = 1782021, upload-time = "2026-06-07T21:08:41.407Z" }, - { url = "https://files.pythonhosted.org/packages/c1/af/14bb5843eccbe234f4dfb78ab73e549d99727247e62ae5d62cbd22eaf5b0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444", size = 1742574, upload-time = "2026-06-07T21:08:43.795Z" }, - { url = "https://files.pythonhosted.org/packages/f2/1e/fbeb7af9210a67ac0f9c9bec0f8f4568497924e33137a3d5b48e1cf85f3f/aiohttp-3.14.1-cp314-cp314-win32.whl", hash = "sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0", size = 457773, upload-time = "2026-06-07T21:08:46.168Z" }, - { url = "https://files.pythonhosted.org/packages/f0/2b/13e8d741a9ec5db7d900c060554cf8352ab85e44e2a4469ebb9d377bda17/aiohttp-3.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719", size = 485001, upload-time = "2026-06-07T21:08:48.401Z" }, - { url = "https://files.pythonhosted.org/packages/df/30/491acfa2c4d6c3ff59c49a14fc1b50be3241e25bbb0c84c09e2da4d11395/aiohttp-3.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec", size = 453809, upload-time = "2026-06-07T21:08:50.7Z" }, - { url = "https://files.pythonhosted.org/packages/34/e3/19dbe1a1f4cc6230eb9e314de7fe68053b0992f9302b27d12141a0b5db53/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2", size = 793320, upload-time = "2026-06-07T21:08:52.775Z" }, - { url = "https://files.pythonhosted.org/packages/7f/20/1b7182219ba1b108430d6e4dc53d25ae02dcfcf5a045b33af4e8c5167527/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340", size = 529077, upload-time = "2026-06-07T21:08:55Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c8/14ce60ec31a2e5f5274bb17d383a6f7a3aabca31ac04eee05585bbadab16/aiohttp-3.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d", size = 532476, upload-time = "2026-06-07T21:08:57.176Z" }, - { url = "https://files.pythonhosted.org/packages/7e/02/9ac85e081e53da2e061b02fa7758fe0a12d17b8ce2d1f5e6c7cb76730328/aiohttp-3.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94", size = 1922347, upload-time = "2026-06-07T21:08:59.563Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3e/d3ba07a0ab38b5389e10bec4362d21e10a4f667cba2d79ba30837b3a5059/aiohttp-3.14.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc", size = 1786465, upload-time = "2026-06-07T21:09:01.909Z" }, - { url = "https://files.pythonhosted.org/packages/0b/cb/e2ee978a00cfb2df829704a69528b18154eba5939f45bc1efa8f33aee4c5/aiohttp-3.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251", size = 1909423, upload-time = "2026-06-07T21:09:04.357Z" }, - { url = "https://files.pythonhosted.org/packages/73/5d/1430334858b1022b58ae50399a918f0bd6fe8fa7fa183598d657ff61e040/aiohttp-3.14.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1", size = 2001906, upload-time = "2026-06-07T21:09:06.722Z" }, - { url = "https://files.pythonhosted.org/packages/66/4e/560c7472d3d198a23aa5c8b19a5115bf6a9b77b7d3e4bb363da320430ad2/aiohttp-3.14.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3", size = 1877095, upload-time = "2026-06-07T21:09:09.011Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f1/4745806578d447db4a784a8591e2dae3afdfc2bcb96f8f81271b13df6543/aiohttp-3.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c", size = 1676222, upload-time = "2026-06-07T21:09:11.461Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c9/48255813cca749a229ef0ab476004ec623728ad79a9c0840616f6c076325/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203", size = 1842922, upload-time = "2026-06-07T21:09:14.118Z" }, - { url = "https://files.pythonhosted.org/packages/3d/c0/bbd054e2bee909f529523a5af3891052606af5143c09f5f183ec3b234676/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1", size = 1825035, upload-time = "2026-06-07T21:09:16.447Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ae/90395d4376deceb74e09ec26b6adf7d2015a6f8802d6d84446af860fef04/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665", size = 1849512, upload-time = "2026-06-07T21:09:18.742Z" }, - { url = "https://files.pythonhosted.org/packages/93/bd/fb25f3049957553d4ce0ba6ae480aa2f592a6985497fca590837d16c1be0/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1", size = 1668571, upload-time = "2026-06-07T21:09:21.458Z" }, - { url = "https://files.pythonhosted.org/packages/3f/22/7f73303d64dd567ff3addca90b556690ed1233a47b8f55d242fb90af3681/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d", size = 1881159, upload-time = "2026-06-07T21:09:23.813Z" }, - { url = "https://files.pythonhosted.org/packages/44/be/0474c5a8b5640e1e4aa1923430a91f4151be82e511373fe764189b89aef5/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c", size = 1841409, upload-time = "2026-06-07T21:09:26.207Z" }, - { url = "https://files.pythonhosted.org/packages/7b/3c/bb4a7cba26956cb3da4553cc2056cf67be5b5ff6e6d8fa4fbdff73bfb7ae/aiohttp-3.14.1-cp314-cp314t-win32.whl", hash = "sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365", size = 494166, upload-time = "2026-06-07T21:09:28.505Z" }, - { url = "https://files.pythonhosted.org/packages/8a/84/ec80c2c1f66a952555a9f86df6b33af65108a6febfa0471b69013a12f807/aiohttp-3.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9", size = 530255, upload-time = "2026-06-07T21:09:30.843Z" }, - { url = "https://files.pythonhosted.org/packages/2a/71/6e22be134a4061ada85a92951b842f2657f17d926b727f3f94c56ae963d6/aiohttp-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6", size = 469640, upload-time = "2026-06-07T21:09:33.028Z" }, -] - -[[package]] -name = "aiosignal" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "frozenlist" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, -] - [[package]] name = "alabaster" version = "1.0.0" @@ -302,15 +156,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] -[[package]] -name = "annotated-doc" -version = "0.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, -] - [[package]] name = "annotated-types" version = "0.7.0" @@ -352,15 +197,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, ] -[[package]] -name = "appdirs" -version = "1.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, -] - [[package]] name = "ast-serialize" version = "0.5.0" @@ -428,85 +264,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] -[[package]] -name = "backoff" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, -] - -[[package]] -name = "bcrypt" -version = "5.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806, upload-time = "2025-09-25T19:49:05.102Z" }, - { url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626, upload-time = "2025-09-25T19:49:06.723Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853, upload-time = "2025-09-25T19:49:08.028Z" }, - { url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793, upload-time = "2025-09-25T19:49:09.727Z" }, - { url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930, upload-time = "2025-09-25T19:49:11.204Z" }, - { url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194, upload-time = "2025-09-25T19:49:12.524Z" }, - { url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381, upload-time = "2025-09-25T19:49:14.308Z" }, - { url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750, upload-time = "2025-09-25T19:49:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757, upload-time = "2025-09-25T19:49:17.244Z" }, - { url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740, upload-time = "2025-09-25T19:49:18.693Z" }, - { url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197, upload-time = "2025-09-25T19:49:20.523Z" }, - { url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974, upload-time = "2025-09-25T19:49:22.254Z" }, - { url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498, upload-time = "2025-09-25T19:49:24.134Z" }, - { url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853, upload-time = "2025-09-25T19:49:25.702Z" }, - { url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626, upload-time = "2025-09-25T19:49:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862, upload-time = "2025-09-25T19:49:28.365Z" }, - { url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544, upload-time = "2025-09-25T19:49:30.39Z" }, - { url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787, upload-time = "2025-09-25T19:49:32.144Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753, upload-time = "2025-09-25T19:49:33.885Z" }, - { url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587, upload-time = "2025-09-25T19:49:35.144Z" }, - { url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178, upload-time = "2025-09-25T19:49:36.793Z" }, - { url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295, upload-time = "2025-09-25T19:49:38.164Z" }, - { url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700, upload-time = "2025-09-25T19:49:39.917Z" }, - { url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034, upload-time = "2025-09-25T19:49:41.227Z" }, - { url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766, upload-time = "2025-09-25T19:49:43.08Z" }, - { url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449, upload-time = "2025-09-25T19:49:44.971Z" }, - { url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310, upload-time = "2025-09-25T19:49:46.162Z" }, - { url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761, upload-time = "2025-09-25T19:49:47.345Z" }, - { url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" }, - { url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" }, - { url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" }, - { url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" }, - { url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" }, - { url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" }, - { url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" }, - { url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" }, - { url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" }, - { url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" }, - { url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" }, - { url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" }, - { url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" }, - { url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" }, - { url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" }, - { url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" }, - { url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" }, - { url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" }, - { url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" }, - { url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" }, - { url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" }, - { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" }, - { url = "https://files.pythonhosted.org/packages/8a/75/4aa9f5a4d40d762892066ba1046000b329c7cd58e888a6db878019b282dc/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7edda91d5ab52b15636d9c30da87d2cc84f426c72b9dba7a9b4fe142ba11f534", size = 271180, upload-time = "2025-09-25T19:50:38.575Z" }, - { url = "https://files.pythonhosted.org/packages/54/79/875f9558179573d40a9cc743038ac2bf67dfb79cecb1e8b5d70e88c94c3d/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:046ad6db88edb3c5ece4369af997938fb1c19d6a699b9c1b27b0db432faae4c4", size = 273791, upload-time = "2025-09-25T19:50:39.913Z" }, - { url = "https://files.pythonhosted.org/packages/bc/fe/975adb8c216174bf70fc17535f75e85ac06ed5252ea077be10d9cff5ce24/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dcd58e2b3a908b5ecc9b9df2f0085592506ac2d5110786018ee5e160f28e0911", size = 270746, upload-time = "2025-09-25T19:50:43.306Z" }, - { url = "https://files.pythonhosted.org/packages/e4/f8/972c96f5a2b6c4b3deca57009d93e946bbdbe2241dca9806d502f29dd3ee/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:6b8f520b61e8781efee73cba14e3e8c9556ccfb375623f4f97429544734545b4", size = 273375, upload-time = "2025-09-25T19:50:45.43Z" }, -] - [[package]] name = "beautifulsoup4" version = "4.15.0" @@ -548,20 +305,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/19/934f81592527a3f7f9b943c893e334c721a4644948642bc33885d584e9ec/botocore-1.43.36-py3-none-any.whl", hash = "sha256:3c65fdc39ed01d8dfde1e961b34038aed03c459f8ddf80717a12ac006475e49d", size = 15313630, upload-time = "2026-06-23T02:46:59.327Z" }, ] -[[package]] -name = "build" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, - { name = "packaging" }, - { name = "pyproject-hooks" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" }, -] - [[package]] name = "cachetools" version = "7.1.4" @@ -739,49 +482,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] -[[package]] -name = "chromadb" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "bcrypt" }, - { name = "build" }, - { name = "grpcio" }, - { name = "httpx" }, - { name = "importlib-resources" }, - { name = "jsonschema" }, - { name = "kubernetes" }, - { name = "mmh3" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "onnxruntime" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-proto-grpc" }, - { name = "opentelemetry-sdk" }, - { name = "orjson" }, - { name = "overrides" }, - { name = "posthog" }, - { name = "pybase64" }, - { name = "pydantic" }, - { name = "pypika" }, - { name = "pyyaml" }, - { name = "rich" }, - { name = "tenacity" }, - { name = "tokenizers" }, - { name = "tqdm" }, - { name = "typer" }, - { name = "typing-extensions" }, - { name = "uvicorn", extra = ["standard"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7f/48/11851dddeadad6abe36ee071fedc99b5bdd2c324df3afa8cb952ae02798b/chromadb-1.1.1.tar.gz", hash = "sha256:ebfce0122753e306a76f1e291d4ddaebe5f01b5979b97ae0bc80b1d4024ff223", size = 1338109, upload-time = "2025-10-05T02:49:14.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/59/0d881a9b7eb63d8d2446cf67fcbb53fb8ae34991759d2b6024a067e90a9a/chromadb-1.1.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:27fe0e25ef0f83fb09c30355ab084fe6f246808a7ea29e8c19e85cf45785b90d", size = 19175479, upload-time = "2025-10-05T02:49:12.525Z" }, - { url = "https://files.pythonhosted.org/packages/94/4f/5a9fa317c84c98e70af48f74b00aa25589626c03a0428b4381b2095f3d73/chromadb-1.1.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:95aed58869683f12e7dcbf68b039fe5f576dbe9d1b86b8f4d014c9d077ccafd2", size = 18267188, upload-time = "2025-10-05T02:49:09.236Z" }, - { url = "https://files.pythonhosted.org/packages/45/1a/02defe2f1c8d1daedb084bbe85f5b6083510a3ba192ed57797a3649a4310/chromadb-1.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06776dad41389a00e7d63d936c3a15c179d502becaf99f75745ee11b062c9b6a", size = 18855754, upload-time = "2025-10-05T02:49:03.299Z" }, - { url = "https://files.pythonhosted.org/packages/5a/0d/80be82717e5dc19839af24558494811b6f2af2b261a8f21c51b872193b09/chromadb-1.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bba0096a7f5e975875ead23a91c0d41d977fbd3767f60d3305a011b0ace7afd3", size = 19893681, upload-time = "2025-10-05T02:49:06.481Z" }, - { url = "https://files.pythonhosted.org/packages/2d/6e/956e62975305a4e31daf6114a73b3b0683a8f36f8d70b20aabd466770edb/chromadb-1.1.1-cp39-abi3-win_amd64.whl", hash = "sha256:a77aa026a73a18181fd89bbbdb86191c9a82fd42aa0b549ff18d8cae56394c8b", size = 19844042, upload-time = "2025-10-05T02:49:16.925Z" }, -] - [[package]] name = "click" version = "8.4.2" @@ -892,67 +592,6 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] -[[package]] -name = "crewai" -version = "1.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "appdirs" }, - { name = "chromadb" }, - { name = "click" }, - { name = "instructor" }, - { name = "json-repair" }, - { name = "json5" }, - { name = "jsonref" }, - { name = "mcp" }, - { name = "openai" }, - { name = "openpyxl" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-proto-http" }, - { name = "opentelemetry-sdk" }, - { name = "pdfplumber" }, - { name = "portalocker" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "pyjwt" }, - { name = "python-dotenv" }, - { name = "regex" }, - { name = "tokenizers" }, - { name = "tomli" }, - { name = "tomli-w" }, - { name = "uv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1e/c4/37f5e8e0ccb2804a3e2acc0ccf58f82dc9415a08fad71a3868cdf830c669/crewai-1.6.1.tar.gz", hash = "sha256:b7d73a8a333abf71b30ab20c54086004cd0c016dfd86bba9c035ad5eb31e22a7", size = 4177912, upload-time = "2025-11-29T01:58:25.573Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/87/8ab9924b79025165ed7f1b04a90f9b80137d18ceae9b8e34445a8495320c/crewai-1.6.1-py3-none-any.whl", hash = "sha256:8cec403ab89183bda28b830c722b6bc22457a2151a6aa46f07730e6fe7ab2723", size = 642861, upload-time = "2025-11-29T01:58:23.232Z" }, -] - -[package.optional-dependencies] -anthropic = [ - { name = "anthropic" }, -] - -[[package]] -name = "crewai-tools" -version = "1.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, - { name = "crewai" }, - { name = "docker" }, - { name = "lancedb" }, - { name = "pymupdf" }, - { name = "python-docx" }, - { name = "pytube" }, - { name = "requests" }, - { name = "tiktoken" }, - { name = "youtube-transcript-api" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/e2/039d47c1d5266a807c9f4f8d4b927fab1ebfb60989ec6b65fcd88070a510/crewai_tools-1.6.1.tar.gz", hash = "sha256:8724400b85b0a97de09fe681b1d0bf4334e3e68bcf5ede8a056e2beed0227907", size = 805758, upload-time = "2025-11-29T01:58:29.613Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/d1/38d0bc01712cf086cd80fe778ed15ff9b57dc5cf4c0b11f1f40d88d4b50a/crewai_tools-1.6.1-py3-none-any.whl", hash = "sha256:62996840db257af9c471f9f4191b4268cb4dd7006b381670b968ac55973e08af", size = 764950, upload-time = "2025-11-29T01:58:27.441Z" }, -] - [[package]] name = "cryptography" version = "49.0.0" @@ -1009,27 +648,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" }, ] -[[package]] -name = "defusedxml" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, -] - -[[package]] -name = "deprecation" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, -] - [[package]] name = "distlib" version = "0.4.3" @@ -1048,20 +666,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] -[[package]] -name = "docker" -version = "7.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "requests" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, -] - [[package]] name = "docstring-parser" version = "0.18.0" @@ -1080,24 +684,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, ] -[[package]] -name = "durationpy" -version = "0.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/e44218c2b394e31a6dd0d6b095c4e1f32d0be54c2a4b250032d717647bab/durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba", size = 3335, upload-time = "2025-05-17T13:52:37.26Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" }, -] - -[[package]] -name = "et-xmlfile" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, -] - [[package]] name = "execnet" version = "2.1.2" @@ -1116,128 +702,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, ] -[[package]] -name = "flatbuffers" -version = "25.12.19" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" }, -] - -[[package]] -name = "frozenlist" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, - { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, - { 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/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, - { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, - { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, - { 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/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, - { 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/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, - { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, - { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, - { 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/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, - { 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/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, - { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, - { 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/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, - { 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/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, - { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, - { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, - { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, - { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, - { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, - { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, - { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, - { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, - { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, - { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, - { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, - { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, - { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, - { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, - { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, - { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, - { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, - { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, - { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, - { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, - { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, - { 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" }, -] - -[[package]] -name = "fsspec" -version = "2026.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/a1/ae4e3e5003468d6391d2c77b6fa1cd73bd5d13511d81c642d7b28ac90ed4/fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a", size = 313646, upload-time = "2026-06-16T01:57:28.105Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, -] - [[package]] name = "furo" version = "2025.12.19" @@ -1254,69 +718,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" }, ] -[[package]] -name = "googleapis-common-protos" -version = "1.75.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b5/c8/f439cffde755cffa462bfbb156278fa6f9d09119719af9814b858fd4f81f/googleapis_common_protos-1.75.0.tar.gz", hash = "sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd", size = 151035, upload-time = "2026-05-07T08:04:49.423Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/c8/e2645aa8ed02fd4c7a2f59d68783b65b1f3cbdfe39a6308e156509d1fee8/googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed", size = 300631, upload-time = "2026-05-07T08:03:30.345Z" }, -] - -[[package]] -name = "grpcio" -version = "1.81.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b0/b5/1ff353970a87eda4c98251e34d2dfd214abd4982dc89119c9252a2a482d2/grpcio-1.81.1.tar.gz", hash = "sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b", size = 13026582, upload-time = "2026-06-11T12:46:51.673Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/ea/1c2fa386b718ff493225e61cfc052ef400b4d6ffc54cbe261026432624b5/grpcio-1.81.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:d71d30f2d92f67d944631c523713934fee37292469e182ebcd2c1dd8a64ce53f", size = 6093112, upload-time = "2026-06-11T12:44:52.131Z" }, - { url = "https://files.pythonhosted.org/packages/2b/18/acf45fa8bd1bc5d7b0c2fd3dc4c209379fbd5bb396b440b68a83342226b7/grpcio-1.81.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b137f4bf3ada9dc44d411478decc6ff09a79ed30b306cd2abaa98408c3588137", size = 12074277, upload-time = "2026-06-11T12:44:55.354Z" }, - { url = "https://files.pythonhosted.org/packages/48/d7/ee86a60699b7db039f772a2c4a7e4facc7138984ff42c0130933a0063884/grpcio-1.81.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a3acb384427816dd5d470f47e62137b87f74da694faa8a50147012cf40df276a", size = 6640348, upload-time = "2026-06-11T12:44:59.223Z" }, - { url = "https://files.pythonhosted.org/packages/26/ee/d2de5e47378ffc207d476c230fea3be4d2601edbce9995f4fe45535d4896/grpcio-1.81.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f9a0ebbe45c29b5e5866593c12b78bd9035f0f0f0d4bc8361680cd580d99db49", size = 7331842, upload-time = "2026-06-11T12:45:02.001Z" }, - { url = "https://files.pythonhosted.org/packages/23/d6/abeda5c2b896a0b341584fe5ac411bbf72e197a9a374c355fb90965e08d2/grpcio-1.81.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a37165cc80b1a368384b383e63a4c38116a10467ae44c904d2d7468c4470ec2", size = 6842229, upload-time = "2026-06-11T12:45:04.76Z" }, - { url = "https://files.pythonhosted.org/packages/10/1c/1f0da7d590b4aeee006826ba568d0e419ca14b23e18f901a3da3e9fba613/grpcio-1.81.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6282caffb41ec326d4cb67ca9cf53b739d1b2f975a2acb498c7418e9f7d9a416", size = 7446096, upload-time = "2026-06-11T12:45:07.499Z" }, - { url = "https://files.pythonhosted.org/packages/6a/81/5c505d508f7c887aa7982d21443a4126597c80d34b0bcf40f9cec576d7f3/grpcio-1.81.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a35009284d0d3d5c2c9601c164a911b8b4331608d98a9a66d47d97bb2f522b70", size = 8445238, upload-time = "2026-06-11T12:45:10.243Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b2/524847365122ee509ca17bcc4e092198b700e94af7bfd5bb5e6dd9f3ee66/grpcio-1.81.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1b22c80559854b789a01fd89e8929b3798a156c0829b5282a8939f33ad4115ad", size = 7873989, upload-time = "2026-06-11T12:45:13.102Z" }, - { url = "https://files.pythonhosted.org/packages/18/fa/07c037c50b006909d1d13a5848774f8aa7b242f70dc03a035c64eea0e6db/grpcio-1.81.1-cp311-cp311-win32.whl", hash = "sha256:428bec0161b48d8cf583c068591bc0016d0d9cfff52462b72b3884861ea768c5", size = 4202223, upload-time = "2026-06-11T12:45:16.166Z" }, - { url = "https://files.pythonhosted.org/packages/41/ed/6bff15376920942fac6b95b9802752b837437172c9e8fc2d3170546b89cc/grpcio-1.81.1-cp311-cp311-win_amd64.whl", hash = "sha256:30e825f6848d9f18bba350ed6c75c1b02a0b5184474a31db9a32b1fa66fd8c79", size = 4941303, upload-time = "2026-06-11T12:45:18.724Z" }, - { url = "https://files.pythonhosted.org/packages/85/07/9a979c81738863a738dc23d65177056e71fbb2db817740ed870b33434e7a/grpcio-1.81.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115", size = 6053264, upload-time = "2026-06-11T12:45:21.017Z" }, - { url = "https://files.pythonhosted.org/packages/75/95/539706ca0d3bd40dbad583dc56fd883da941f37556b629132da5762781b9/grpcio-1.81.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3", size = 12052560, upload-time = "2026-06-11T12:45:23.652Z" }, - { url = "https://files.pythonhosted.org/packages/e0/44/f257b7e0bd69c93b06c6cb8ac8d1b901ccb42bedabd83c1a4c77a71f8810/grpcio-1.81.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2", size = 6595983, upload-time = "2026-06-11T12:45:26.963Z" }, - { url = "https://files.pythonhosted.org/packages/b9/f3/19782aa04c960968bef8c5539329d8e3bbc3364e2e46d19eb5e5cc5e43b7/grpcio-1.81.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2c2e2ae6867c2966b8daccc836d54a13218e0007e9a490aeb81dd05be64d22d7", size = 7303455, upload-time = "2026-06-11T12:45:29.707Z" }, - { url = "https://files.pythonhosted.org/packages/eb/8c/dea020b6d91508cd84463917a63149ec196ee7db505d032ae43fcb3303b9/grpcio-1.81.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:766bc7c9a9c340342f4c864ccbda8e78111e4751f13b895812b9c148fb79e9d0", size = 6809167, upload-time = "2026-06-11T12:45:32.52Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c7/3030dd940408083bd32cd95d634777a71605ade4887154d93e8a89244946/grpcio-1.81.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b259a04a737cb3496be0901328eb8b7552ed8df4865d8c8f1cf1bffcfc0776a3", size = 7412536, upload-time = "2026-06-11T12:45:35.403Z" }, - { url = "https://files.pythonhosted.org/packages/e0/dd/1172a9e42b168edcafefad6115346ef619a3fc02158bb170e66ced24bcdd/grpcio-1.81.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:85b10a45b8993d195c4f3ff57025b8d1e11834909ee475c403bfa60cb4caefaf", size = 8408276, upload-time = "2026-06-11T12:45:37.78Z" }, - { url = "https://files.pythonhosted.org/packages/25/7a/71437c7f3596e5246155c515852795a85a1a8d228190212432b13b97a95d/grpcio-1.81.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8ea1936c26b99999b27479853039a7f34713f56c49375ad52b38535ec93a796c", size = 7849660, upload-time = "2026-06-11T12:45:40.627Z" }, - { url = "https://files.pythonhosted.org/packages/65/40/7debc0da45d2efebafb82da75644be347497fe4ee250514b8cd3b86ae8bf/grpcio-1.81.1-cp312-cp312-win32.whl", hash = "sha256:a185a04039df6cae8648bc8ab6d6fde7bf94f7188ecf7828e76ac52eef1e41d6", size = 4185819, upload-time = "2026-06-11T12:45:43.027Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b9/8fe3ba5ed462067774ebc1f9c7f26aa7ebcc280ddd476be107153de1339e/grpcio-1.81.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ad74f8bb1a18963914c5452d289422830b39459e8776ebbcd207be1fbfb1d94", size = 4930461, upload-time = "2026-06-11T12:45:45.775Z" }, - { url = "https://files.pythonhosted.org/packages/7a/42/dcc2e4b600538ef18327c0839d56b7d3c3812337c5d710df5877dbb39b1e/grpcio-1.81.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe", size = 6054466, upload-time = "2026-06-11T12:45:48.43Z" }, - { url = "https://files.pythonhosted.org/packages/7b/4a/a36e03210183a8a7d4c80c3936acee679f4bd77d5861f369db47b2cc5f05/grpcio-1.81.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e", size = 12048795, upload-time = "2026-06-11T12:45:54.011Z" }, - { url = "https://files.pythonhosted.org/packages/b0/d5/d68e30b29098f63beab6fe501100fe82674ff142b32c672532da86a99b3a/grpcio-1.81.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0", size = 6599094, upload-time = "2026-06-11T12:45:57.799Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b3/e837954d279754f638a11cca5dcf6b24a005efb398984cefaf7735945a54/grpcio-1.81.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14", size = 7307182, upload-time = "2026-06-11T12:46:00.568Z" }, - { url = "https://files.pythonhosted.org/packages/0d/1e/b47957057e729adc6cdf519a47f8be2562b7140e280f1418443eb4022192/grpcio-1.81.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae", size = 6810962, upload-time = "2026-06-11T12:46:03.312Z" }, - { url = "https://files.pythonhosted.org/packages/40/26/569868e364e05b19ec8f969da53d230bcd89c962cd198f7c29943155c4d3/grpcio-1.81.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5", size = 7415698, upload-time = "2026-06-11T12:46:06.005Z" }, - { url = "https://files.pythonhosted.org/packages/36/0c/5440a0582cb5653fc42a6e262eeb22700943313f8076f9dc927491b20a59/grpcio-1.81.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb", size = 8407779, upload-time = "2026-06-11T12:46:08.84Z" }, - { url = "https://files.pythonhosted.org/packages/ff/aa/66fe9f39871d766987d869a03ee0842a026f499c7b1e62decb9e78a8088e/grpcio-1.81.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7", size = 7844521, upload-time = "2026-06-11T12:46:12.171Z" }, - { url = "https://files.pythonhosted.org/packages/f0/9e/69bb7194861bcd28fb3193261d4f9c3831b4446993f002cf59068943e7ab/grpcio-1.81.1-cp313-cp313-win32.whl", hash = "sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42", size = 4182786, upload-time = "2026-06-11T12:46:15.192Z" }, - { url = "https://files.pythonhosted.org/packages/0d/20/3da8bb0d637feccdc3e1e419bb511ce93651ce7d54164f95de22cc0b8b34/grpcio-1.81.1-cp313-cp313-win_amd64.whl", hash = "sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60", size = 4928648, upload-time = "2026-06-11T12:46:17.823Z" }, - { url = "https://files.pythonhosted.org/packages/b6/58/19414622b1bf6981bc9c05a365bd548e71876c89000083b3af489251e9c0/grpcio-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b", size = 6055336, upload-time = "2026-06-11T12:46:20.557Z" }, - { url = "https://files.pythonhosted.org/packages/32/f1/2ec88adb92b0eba970dd0e0e7dd086341daa3c75eba4f735f9e44bf684b0/grpcio-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e", size = 12056279, upload-time = "2026-06-11T12:46:24.255Z" }, - { url = "https://files.pythonhosted.org/packages/41/36/e8c5f8c6ec71de73733695ebc809e98b178b534ec6d8eaa31a7ebab4ad4c/grpcio-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27", size = 6608225, upload-time = "2026-06-11T12:46:27.601Z" }, - { url = "https://files.pythonhosted.org/packages/30/22/96fc577a845ab093326d9ab1adb874bd4936c8cf98ac8ed2f3db13a0a2fb/grpcio-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854", size = 7306576, upload-time = "2026-06-11T12:46:30.514Z" }, - { url = "https://files.pythonhosted.org/packages/76/7b/61dab5d5969f28d97fb1009cead1df0a5cd987d3315e1b37f18a4449f8bc/grpcio-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6", size = 6812165, upload-time = "2026-06-11T12:46:33.699Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/6e501929d4f5f96462fd82fd9f0f06e5f9612207582b862868d68757b27d/grpcio-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5", size = 7422962, upload-time = "2026-06-11T12:46:36.511Z" }, - { url = "https://files.pythonhosted.org/packages/2a/7e/f2157589e66daa78ebb3165942d05a08bdea93b9d11c2bc1e172aef89685/grpcio-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0", size = 8408176, upload-time = "2026-06-11T12:46:39.803Z" }, - { url = "https://files.pythonhosted.org/packages/da/df/c6717fef716e00d235ffb96123baf6dce76d6004f6233fa767c502861460/grpcio-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190", size = 7846681, upload-time = "2026-06-11T12:46:43.013Z" }, - { url = "https://files.pythonhosted.org/packages/36/84/3502e9f210a6a5c4438c8aca3f88edd2e04f6a27f3d41b26cf0a0024b096/grpcio-1.81.1-cp314-cp314-win32.whl", hash = "sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f", size = 4264615, upload-time = "2026-06-11T12:46:45.741Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b0/4af731ff7492c68a96e4c71bfd0f4590acde92b31c6fe4894e6465c10ff6/grpcio-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821", size = 5070275, upload-time = "2026-06-11T12:46:48.486Z" }, -] - [[package]] name = "h11" version = "0.16.0" @@ -1326,38 +727,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] -[[package]] -name = "hf-xet" -version = "1.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/2d/57fd21d84d93efb4bd0b962383790e19dd1bc053501b4264c97903b4e83e/hf_xet-1.5.1.tar.gz", hash = "sha256:51ef4500dab3764b41135ee1381a4b62ce56fc54d4c92b719b59e597d6df5bf6", size = 876636, upload-time = "2026-06-08T23:02:53.897Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/ee/dd9ba7beae1005e54131b7d45263cc74c8a066d47d354e6d58ae9445a388/hf_xet-1.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:dbf48c0d02cf0b2e568944330c60d9120c272dabe013bd892d48e25bc6797577", size = 4069485, upload-time = "2026-06-08T23:02:13.193Z" }, - { url = "https://files.pythonhosted.org/packages/b6/bc/9cae6cfeb4e03070874e73e5c97c66eb90369d3206b6a2b1ef5f96520888/hf_xet-1.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78e4e5192ad2b674c2e1160b651cb9134db974f8ae1835bdfbfb0166b894a43", size = 3838493, upload-time = "2026-06-08T23:02:15.282Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b4/d5c01e0eb6d9f2ca2dacd84d0d1b71e6cfbb2ef3208c968528e010e9b3d7/hf_xet-1.5.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6f7a04a8ad962422e225bc49fbbac99dc1806764b1f3e54dbd154bffa7593947", size = 4505658, upload-time = "2026-06-08T23:02:17.196Z" }, - { url = "https://files.pythonhosted.org/packages/76/c5/29a7598c0c6383c523dc22186d577f4e04267a626cd95ae60f67c00bfe66/hf_xet-1.5.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d48199c2bf4f8df0adc55d31d1368b6ec0e4d4f45bc86b08038089c23db0bed8", size = 4292822, upload-time = "2026-06-08T23:02:18.608Z" }, - { url = "https://files.pythonhosted.org/packages/04/9a/dceaf6ca69390126b86ea825fb354b93d01163199070b7bd849225de9468/hf_xet-1.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:97f212a88d14bbf573619a74b7fecb238de77d08fc702e54dec6f78276ca3283", size = 4491255, upload-time = "2026-06-08T23:02:20.124Z" }, - { url = "https://files.pythonhosted.org/packages/48/a7/e5a7afaacf6c1791fdbeeac42951fb81c3d2bc482992b115dedcc86d963e/hf_xet-1.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f61e3665892a6c8c5e765395838b8ddf36185da835253d4bc4509a81e49fb342", size = 4711062, upload-time = "2026-06-08T23:02:21.863Z" }, - { url = "https://files.pythonhosted.org/packages/53/49/2802f8433c9742ce281bddc1e65c02c32268ca3098d66828b05e12e45ee2/hf_xet-1.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f4ad3ebd4c32dd2b27099d69dc7b2df821e30767e46fb6ee6a0713778243b8ff", size = 4017205, upload-time = "2026-06-08T23:02:23.495Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5a/50c71195b9fb883659f596e7252faf4c18c58e753a9013bdbf9bac5d2250/hf_xet-1.5.1-cp313-cp313t-win_arm64.whl", hash = "sha256:8298485c1e36e7e67cbd01eeb1376619b7af43d4f1ec245caae306f890a8a32d", size = 3845426, upload-time = "2026-06-08T23:02:25.124Z" }, - { url = "https://files.pythonhosted.org/packages/05/24/5e0c28f80371c17d49fed004597d9d132cb75c1f6f53db2cb95f459d2312/hf_xet-1.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3474760d10e3bb6f92ff3f024fcb00c0b3e4001e9b035c7483e49a5dd17aa70f", size = 4069676, upload-time = "2026-06-08T23:02:26.759Z" }, - { url = "https://files.pythonhosted.org/packages/d2/17/261ba565b6a4d960fb478f61fdf919c0be5824645aaf1c319eca660c1611/hf_xet-1.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6762d89b9e3267dfd502b29b2a327b4525f33b17e7b509a78d94e2151a30ce30", size = 3838509, upload-time = "2026-06-08T23:02:28.573Z" }, - { url = "https://files.pythonhosted.org/packages/4e/44/7ffdc2e184b0d41fc0f683ba3936ef669ab63cf242cf36ef50e57d683668/hf_xet-1.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bf67e6ed10260cef62e852789dc91ebb03f382d5bdc4b1dbeb64763ea275e7d6", size = 4505881, upload-time = "2026-06-08T23:02:30.257Z" }, - { url = "https://files.pythonhosted.org/packages/63/b6/788060d5aa4d5e671f1a31bf69624c314eb2d8babab3aa562f9e5d53444e/hf_xet-1.5.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c6b6cd08ca095058780b50b8ce4d6cbf6787bcf27841705d58a9d32246e3e47a", size = 4292995, upload-time = "2026-06-08T23:02:31.993Z" }, - { url = "https://files.pythonhosted.org/packages/22/93/c5540cbd6b55529b7dc42f6734e88cebee21aefbea34128b66229df56c57/hf_xet-1.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e1af0de8ca6f190d4294a28b88023db64a1e2d1d719cab044baf75bec569e7a9", size = 4491570, upload-time = "2026-06-08T23:02:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/03/f3/9d8ceab30f44f36c1679b1b8683054c71a0dadc787dbf07421891742d3ca/hf_xet-1.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4f561cbbb92f80960772059864b7fb07eae879adde1b2e781ec6f86f6ac26c59", size = 4711565, upload-time = "2026-06-08T23:02:35.454Z" }, - { url = "https://files.pythonhosted.org/packages/cd/54/27ed9a5e2cc583b4df82f75a03a4df8dbf55f5a9fa1f47f1fadfb20dbeac/hf_xet-1.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e7dbb40617410f432182d918e37c12303fe6700fd6aa6c5964e30a535a4461d6", size = 4017343, upload-time = "2026-06-08T23:02:37.14Z" }, - { url = "https://files.pythonhosted.org/packages/ae/12/ecb2fc8d45e767580e3a37faa97cb895608b614965567efb4f18cff67e27/hf_xet-1.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6071d5ccb4d8d2cbd5fea5cc798da4f0ba3f44e25369591c4e89a4987050e61d", size = 3845716, upload-time = "2026-06-08T23:02:39.073Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d8/5e54cf37434759d1f4f2ba9b66077ff9d4c4e1f37b6bd7975da5c40d94ab/hf_xet-1.5.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6abd35c3221eff63836618ddfb954dcf84798603f71d8e33e3ed7b04acfdbe6e", size = 4077794, upload-time = "2026-06-08T23:02:40.656Z" }, - { url = "https://files.pythonhosted.org/packages/35/94/4b2ecfbad8f8b04701a23aefb62f540b9137d058b7e1dbef16a32676f0e9/hf_xet-1.5.1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:94e761bbd266bf4c03cee73753916062665ce8365aa40ed321f45afcb934b41e", size = 3845354, upload-time = "2026-06-08T23:02:42.702Z" }, - { url = "https://files.pythonhosted.org/packages/de/cc/f99f4bc7295023d7bd9ebbfd51f75cc530ca262c1227666268b8208f4b77/hf_xet-1.5.1-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:892e3a3a3aecc12aded8b93cf4f9cd059282c7de0732f7d55026f3abdf474350", size = 4514864, upload-time = "2026-06-08T23:02:44.497Z" }, - { url = "https://files.pythonhosted.org/packages/cd/6e/21f7e5a2381278bd3b7b7a5a4d90038518bb6308a0c1daf5d9f8268bb178/hf_xet-1.5.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a93df2039190502835b1db8cd7e178b0b7b889fe9ab51299d5ced26e0dd879a4", size = 4303784, upload-time = "2026-06-08T23:02:46.203Z" }, - { url = "https://files.pythonhosted.org/packages/35/0e/f992bb6927ac1cb30ef74e62268f551f338bc32b2191f7c96a44c6f7283e/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0c97106032ef70467b4f6bc2d0ccc266d7613ee076afc56516c502f87ce1c4a6", size = 4500703, upload-time = "2026-06-08T23:02:47.628Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d1/90a498d05447980b977b1669246eeeeae4cfb0ea3e7a286eaba627f91bf9/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6208adb15d192b90e4c2ad2a27ed864359b2cb0f2494eb6d7c7f3699ac02e2bf", size = 4719498, upload-time = "2026-06-08T23:02:49.268Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b6/20f99cfe97cc663a711f7b33cc21d4793e51968e9a26125b4afcd77315ba/hf_xet-1.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:f7b3002f95d1c13e24bcb4537baa8f0eb3838957067c91bb4959bc004a6435f5", size = 4026419, upload-time = "2026-06-08T23:02:50.829Z" }, - { url = "https://files.pythonhosted.org/packages/f9/fa/77453694888f03e5a8c8852d1514a0894d8e81c622d39edbaf308ea0dcf4/hf_xet-1.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:93d090b57b211133f6c0dab0205ef5cb6d89162979ba75a74845045cc3063b8e", size = 3855178, upload-time = "2026-06-08T23:02:52.452Z" }, -] - [[package]] name = "httpcore" version = "1.0.9" @@ -1371,49 +740,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] -[[package]] -name = "httptools" -version = "0.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/d2/c3eedaef57de65c3cc5f8dc244cf12d09c84ad258a479055aad6db23206c/httptools-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168", size = 208428, upload-time = "2026-05-25T22:16:59.717Z" }, - { url = "https://files.pythonhosted.org/packages/f1/94/dfe435d90d0ef61ec0f2cc3d480eef78c59727c6c2ce039f433882f6131a/httptools-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d", size = 113366, upload-time = "2026-05-25T22:17:00.795Z" }, - { url = "https://files.pythonhosted.org/packages/cc/d4/13025f1a56e615dcb331e0bbe2d9a1143212b58c263385fc5d2e558f5bac/httptools-0.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376", size = 464676, upload-time = "2026-05-25T22:17:02.014Z" }, - { url = "https://files.pythonhosted.org/packages/bf/95/4c1c26c0b985f8a3331682d802598f14e32dc41bf7509266eb2c04ad4801/httptools-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d", size = 464235, upload-time = "2026-05-25T22:17:03.109Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/6735be2b0ca527718c431cdb8e5f70c3862c0844a687df0f572c51e11497/httptools-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085", size = 449809, upload-time = "2026-05-25T22:17:04.443Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f9/5811c74f37a758c8a4aa3dc430375119d335947e883efc4664d8f3559a41/httptools-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124", size = 452174, upload-time = "2026-05-25T22:17:05.476Z" }, - { url = "https://files.pythonhosted.org/packages/cc/94/97b75870dea07b71e3ec535cebe525b08d723152e4c7d13fa887e51f4de2/httptools-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07", size = 90991, upload-time = "2026-05-25T22:17:06.75Z" }, - { url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" }, - { url = "https://files.pythonhosted.org/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" }, - { url = "https://files.pythonhosted.org/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" }, - { url = "https://files.pythonhosted.org/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" }, - { url = "https://files.pythonhosted.org/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" }, - { url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" }, - { url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" }, - { url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" }, - { url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" }, - { url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" }, - { url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" }, - { url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" }, - { url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" }, - { url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" }, - { url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" }, - { url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" }, - { url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" }, - { url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" }, - { url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" }, - { url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" }, - { url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" }, -] - [[package]] name = "httpx" version = "0.28.1" @@ -1438,27 +764,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, ] -[[package]] -name = "huggingface-hub" -version = "1.21.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "filelock" }, - { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, - { name = "httpx" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "tqdm" }, - { name = "typer" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8f/77/ce3331f40cb2d021fe9b24c46c41e72faf74493621138e5eddac12bf5e1c/huggingface_hub-1.21.0.tar.gz", hash = "sha256:a44f222cd8f2f7c2eade30b5e7a04cac984a3235fa61ea87a0a5a31db77d561f", size = 861572, upload-time = "2026-06-25T13:09:26.356Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/85/b505a99a133d9f99d21af182af416e9baef70bdeef019983479651e494c2/huggingface_hub-1.21.0-py3-none-any.whl", hash = "sha256:eadaa3678c512c82aea69e8675d90a184861e68de32f1105668628b4dce0e7cd", size = 721078, upload-time = "2026-06-25T13:09:24.402Z" }, -] - [[package]] name = "idna" version = "3.18" @@ -1477,15 +782,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, ] -[[package]] -name = "importlib-resources" -version = "7.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/06/b56dfa750b44e86157093bc8fca0ab81dccbf5260510de4eaf1cb69b5b99/importlib_resources-7.1.0.tar.gz", hash = "sha256:0722d4c6212489c530f2a145a34c0a7a3b4721bc96a15fada5930e2a0b760708", size = 44985, upload-time = "2026-04-12T16:36:09.232Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/db/55a262f3606bebcae07cc14095338471ad7c0bbcaa37707e6f0ee49725b7/importlib_resources-7.1.0-py3-none-any.whl", hash = "sha256:1bd7b48b4088eddb2cd16382150bb515af0bd2c70128194392725f82ad2c96a1", size = 37232, upload-time = "2026-04-12T16:36:08.219Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0" @@ -1495,28 +791,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] -[[package]] -name = "instructor" -version = "1.15.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "docstring-parser" }, - { name = "jinja2" }, - { name = "jiter" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-core" }, - { name = "requests" }, - { name = "rich" }, - { name = "tenacity" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3c/5b/40d7906e46aa8607b7259fdfb0114865d87ba52e7554499d8f4830c16d63/instructor-1.15.3.tar.gz", hash = "sha256:2d13f1201a2a9c0a350a6286990f7609b29d04efd6da8f641354a110a3f8bb0b", size = 70049094, upload-time = "2026-06-15T05:51:17.519Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/b3/f999fa377b2e639b0b212216b9f493e72de8ed8ed64b803fb1fe6b776f99/instructor-1.15.3-py3-none-any.whl", hash = "sha256:12553651b77b933ac3e96e53c18d0d3e7c5e627d1ea35625c1e4e11be0d41c59", size = 252418, upload-time = "2026-06-15T05:51:21.428Z" }, -] - [[package]] name = "jinja2" version = "3.1.6" @@ -1628,24 +902,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, ] -[[package]] -name = "json-repair" -version = "0.25.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/cb/50b0bbc3e504ef875aea0062cdc108077e4923fb8c1209c70c80dc043933/json_repair-0.25.2.tar.gz", hash = "sha256:161a56d7e6bbfd4cad3a614087e3e0dbd0e10d402dd20dc7db418432428cb32b", size = 20458, upload-time = "2024-06-27T16:26:15.492Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/43/ac6691c7b5aa7191c964a04ae926d2bb06d9297dba1f2287df5b85cb3715/json_repair-0.25.2-py3-none-any.whl", hash = "sha256:51d67295c3184b6c41a3572689661c6128cef6cfc9fb04db63130709adfc5bf0", size = 12740, upload-time = "2024-06-27T16:26:13.823Z" }, -] - -[[package]] -name = "json5" -version = "0.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/7d/05c46a96a78147ae3bf99c2f4169ce144a70220b8d6fcd56f6ec368b8ce9/json5-0.15.0.tar.gz", hash = "sha256:7424d1f1eb1d56da6e3d70643f53619862b4ce81440bdb8ecfd6f875e5ba4a71", size = 53278, upload-time = "2026-06-19T20:08:27.716Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/be/59527c99478aade6bb33a68d72e6e18dd4e6ff6eacfc7d01bdb15bc76912/json5-0.15.0-py3-none-any.whl", hash = "sha256:56636a30c0e8a4665fe2179c0212f32eae3796dea89ea6f649b9436ecdb39618", size = 36570, upload-time = "2026-06-19T20:08:26.748Z" }, -] - [[package]] name = "jsonpatch" version = "1.33" @@ -1667,15 +923,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, ] -[[package]] -name = "jsonref" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, -] - [[package]] name = "jsonschema" version = "4.26.0" @@ -1703,78 +950,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] -[[package]] -name = "kubernetes" -version = "36.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "certifi" }, - { name = "durationpy" }, - { name = "python-dateutil" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "requests-oauthlib" }, - { name = "six" }, - { name = "urllib3" }, - { name = "websocket-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2f/57/8b538af5076bc3372949d76f70ba3449bdfe52f9e6488170fa5d4f7cbe70/kubernetes-36.0.2.tar.gz", hash = "sha256:03551fcb49cae1f708f63624041e37403545b7aaed10cbf54e2b01a37a5438e3", size = 2336738, upload-time = "2026-06-01T18:20:30.785Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/5c160dbdef7123f8cc97fd8ece7e0198627a426a2a49614845e9086feb8d/kubernetes-36.0.2-py2.py3-none-any.whl", hash = "sha256:faf9b5241b58de0c4a5069f2a0ffc8ac06fece7215156cd3d3ba081a78a858b6", size = 4617568, upload-time = "2026-06-01T18:20:28.737Z" }, -] - -[[package]] -name = "lance-namespace" -version = "0.8.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "lance-namespace-urllib3-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/af/12/f7ab93b29be3edbf5fc3610714bf2d06088e7f4524bfb38dfd6852458b08/lance_namespace-0.8.6.tar.gz", hash = "sha256:18232e721c8188145f4ec9389cc2dfbeeabf54a619d94885ea1b3375bee9f4af", size = 11529, upload-time = "2026-06-12T17:36:41.651Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/1b/5b1668ee2dc8910965f390640359112a31157092fcf8e000b89c79b58708/lance_namespace-0.8.6-py3-none-any.whl", hash = "sha256:571eae34f9aad70e5b05020416c2860889b9ec82993ccd0eb015e7b39c3ea309", size = 13383, upload-time = "2026-06-12T17:36:43.456Z" }, -] - -[[package]] -name = "lance-namespace-urllib3-client" -version = "0.8.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dateutil" }, - { name = "typing-extensions" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c7/80/fb224b4a89c1c1638cde949cb6cce6c3aca7759effbfea46a3d9c3960b21/lance_namespace_urllib3_client-0.8.6.tar.gz", hash = "sha256:b6fb1d306e74a7576e5309919020be744527de484a63dbf5eed10f8b368548df", size = 228772, upload-time = "2026-06-12T17:36:42.609Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/90/1e27de15cd1b16785a1c7312beb0a59e75c8344a815f600f58173a565bd1/lance_namespace_urllib3_client-0.8.6-py3-none-any.whl", hash = "sha256:9d78249c3fb15aa3d15d668f78f04a275af3d08d800a7027492f37996ac4968b", size = 369950, upload-time = "2026-06-12T17:36:40.438Z" }, -] - -[[package]] -name = "lancedb" -version = "0.33.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "deprecation" }, - { name = "lance-namespace" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "overrides", marker = "python_full_version < '3.12'" }, - { name = "packaging" }, - { name = "pyarrow" }, - { name = "pydantic" }, - { name = "tqdm" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/09/2f/d5a4b2a5bb1f800936c76a6d8a4daf127a86fcab621eeb70b574a5adc774/lancedb-0.33.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:d4eaf6fa7c2eac619208f1d396f4de635ee0f535673067118a31c1181575c48b", size = 48338115, upload-time = "2026-05-28T20:37:55.88Z" }, - { url = "https://files.pythonhosted.org/packages/07/12/31787b93a856b2c31382c7771dc22fb05575b70b87c9efe454269f4f0948/lancedb-0.33.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c6c2402ed2744245ae76c4167c0461da0a7a80f1608e0ec491c1548ea2b4302", size = 51162262, upload-time = "2026-05-28T20:37:59.101Z" }, - { url = "https://files.pythonhosted.org/packages/49/b7/081cc29f8e06bf12191b99ab3fe702aceebdb0914476b821a8c0445cacc8/lancedb-0.33.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ebf1ffad811e6254a93931a79489ba1f21f48564bdfa06abae846f5fcaaf3e8", size = 54381368, upload-time = "2026-05-28T20:38:02.2Z" }, - { url = "https://files.pythonhosted.org/packages/1c/bd/e0f4bd621f10ecf96a801b0166e87799ed7ca5a9dbabcef9a6c766a58ef3/lancedb-0.33.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:13da39f80adfea59e5831fe64e4166b2d70a2f843e6507bf644c4fe4c350087c", size = 51188986, upload-time = "2026-05-28T20:38:05.375Z" }, - { url = "https://files.pythonhosted.org/packages/d9/1a/a8647a432ac6aa59cdce1fc061a7050ea4278bcab364539b78af2ecf72d2/lancedb-0.33.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:21b712825f0a00225e8974a41352c4ea84b0899ef8c23b17f672fadc38bd8346", size = 54440958, upload-time = "2026-05-28T20:38:08.474Z" }, - { url = "https://files.pythonhosted.org/packages/08/6c/d0cc8da784cd7ed3b4940a5d1f3e7702e2d99a0a348ba81a376eed782810/lancedb-0.33.0-cp39-abi3-win_amd64.whl", hash = "sha256:4ba78c6202b0f6c2ce8edc7aa470e550d2da56271c7cbdd10428613f1f7126f9", size = 58751944, upload-time = "2026-05-28T20:38:11.549Z" }, -] - [[package]] name = "langchain-anthropic" version = "1.4.8" @@ -1978,108 +1153,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, ] -[[package]] -name = "lxml" -version = "6.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/3b/aab6728cae887456f409b4d75e8a01856e4f04bd510de38052a47768b680/lxml-6.1.1.tar.gz", hash = "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40", size = 4197430, upload-time = "2026-05-18T19:19:06.424Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/b0/83f481780d1548750b8ce2ec824073deef2f452d9cd1a6faff8507e3d16d/lxml-6.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2", size = 8526461, upload-time = "2026-05-18T19:17:25.862Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d5/30fa0f808002c7329397bfbb24e306789c0b29f04aa5842c07b174b4216f/lxml-6.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d", size = 4595375, upload-time = "2026-05-18T19:17:34.555Z" }, - { url = "https://files.pythonhosted.org/packages/4f/d2/edb71cf0e561581a7c5eb2626244320eb04e9f8ce6d563184fd668b45073/lxml-6.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510", size = 4923654, upload-time = "2026-05-18T19:17:42.917Z" }, - { url = "https://files.pythonhosted.org/packages/4c/77/1bc7eeb0de4577d783fb625aa092cc9357883bba35845a3666bf1259f3dc/lxml-6.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a", size = 5067921, upload-time = "2026-05-18T19:17:49.175Z" }, - { url = "https://files.pythonhosted.org/packages/1b/3c/c0690d74bd2bc17bc03b5b0d093569ead597dd0bfa088bf99eef8c24e19c/lxml-6.1.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d", size = 5002456, upload-time = "2026-05-18T19:17:59.715Z" }, - { url = "https://files.pythonhosted.org/packages/66/8d/d1b3271af0c0f1e27e8472a849e4d2c65bc7766884b9ad2da9e76e145c88/lxml-6.1.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8", size = 5202776, upload-time = "2026-05-18T19:18:08.924Z" }, - { url = "https://files.pythonhosted.org/packages/7a/45/689824ffb237fd10125ad273f32b28ff04dc6203c2822c85ff65a93df65e/lxml-6.1.1-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009", size = 5329945, upload-time = "2026-05-18T19:18:13.673Z" }, - { url = "https://files.pythonhosted.org/packages/5d/c0/ef73af53767e958fd87d437c170f272e2f6e6c0f854939f133a895f1e711/lxml-6.1.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6", size = 4659237, upload-time = "2026-05-18T19:18:18.657Z" }, - { url = "https://files.pythonhosted.org/packages/a0/5e/e1158e40397585e91cb0472374a1f63d0926a1ddeaa92f13d1a1ffe306d5/lxml-6.1.1-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8", size = 5265904, upload-time = "2026-05-18T19:18:24.883Z" }, - { url = "https://files.pythonhosted.org/packages/a0/16/8687e5d1400ed1c0bc41dace232ebb7553952b618ea1f2e5fb6e2cfbbe23/lxml-6.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83", size = 5045225, upload-time = "2026-05-18T19:17:20.073Z" }, - { url = "https://files.pythonhosted.org/packages/ca/18/d877bd1ae2e5ffdfd4836565aba350db31feb2f2656d6ce70316ed66a05e/lxml-6.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6", size = 4712721, upload-time = "2026-05-18T19:17:40.512Z" }, - { url = "https://files.pythonhosted.org/packages/44/4d/1f44fd1d770b10dacbf6b5c6e520f4d6e0708744930f719dc04e67cab981/lxml-6.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c", size = 5252549, upload-time = "2026-05-18T19:17:51.236Z" }, - { url = "https://files.pythonhosted.org/packages/64/5d/1d66b84f850089254c230ef6ea6b267a5a54e2e179a5d960036a05d501d7/lxml-6.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08", size = 5226877, upload-time = "2026-05-18T19:18:00.875Z" }, - { url = "https://files.pythonhosted.org/packages/ad/00/84c4b5302d42a2d0184f38d538c8a197f33b52a50bd4f7bcfe990bce3036/lxml-6.1.1-cp311-cp311-win32.whl", hash = "sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621", size = 3594072, upload-time = "2026-05-18T19:17:12.714Z" }, - { url = "https://files.pythonhosted.org/packages/61/9d/2e2f7d876349f45e0f3e29f72da311668853d59b58d473a2dea4f0160135/lxml-6.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28", size = 4025469, upload-time = "2026-05-18T19:17:50.566Z" }, - { url = "https://files.pythonhosted.org/packages/b0/d5/570e6390e4110331e6208b2ba83d1482cc9146808ee118b22824a34c1070/lxml-6.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b", size = 3667640, upload-time = "2026-05-19T19:22:48.293Z" }, - { url = "https://files.pythonhosted.org/packages/6a/6e/c4add832b6fc1e887125b96f880d7b9b70aae5248718e046b1704bcac4b9/lxml-6.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7", size = 8570821, upload-time = "2026-05-18T19:17:42.068Z" }, - { url = "https://files.pythonhosted.org/packages/22/00/ff3009c88e65de8011630acf8ab5a09cb2becd2aaf47fba2f3449f6224e9/lxml-6.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1", size = 4624252, upload-time = "2026-05-18T19:17:47.897Z" }, - { url = "https://files.pythonhosted.org/packages/42/95/bb63f0fd62e554fe078e1fb3c8fe9083c14ddc7ad7fa178d10e57e071ac7/lxml-6.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc", size = 4930746, upload-time = "2026-05-18T19:18:29.637Z" }, - { url = "https://files.pythonhosted.org/packages/eb/99/0013e8d9b5960f4f041cf0b73e2f80c23eb5205b1f7bfb20203243651359/lxml-6.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc", size = 5093723, upload-time = "2026-05-18T19:18:34.168Z" }, - { url = "https://files.pythonhosted.org/packages/29/91/317b332636bfc7bddcff828d41b3307f50043f4b237e40849c333d80fa1a/lxml-6.1.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383", size = 5005557, upload-time = "2026-05-18T19:18:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/42/2f/cc9bf06afe70f9c9093ae60855d9759da9db601ec4080f7473319666ffd7/lxml-6.1.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b", size = 5631036, upload-time = "2026-05-18T19:18:44.858Z" }, - { url = "https://files.pythonhosted.org/packages/08/f6/af32e23e563971ffb0fb86be52bc5be5c2c118858ffc119bf6a9039b173d/lxml-6.1.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818", size = 5240367, upload-time = "2026-05-18T19:18:49.217Z" }, - { url = "https://files.pythonhosted.org/packages/78/83/8555d40948b09ce86f1bd0c68a7ac31d07b1929f92cc1b074006c97ef2d2/lxml-6.1.1-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740", size = 5350171, upload-time = "2026-05-18T19:18:52.779Z" }, - { url = "https://files.pythonhosted.org/packages/63/75/5d92da93729b7bad783689e6496049fa40927b45bec7bf183c981de3ca70/lxml-6.1.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f", size = 4694874, upload-time = "2026-05-18T19:18:55.139Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b5/3aad415a9a25b822e783f15deeb4dffccf5113030f1afa2222dd929313d9/lxml-6.1.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2", size = 5244492, upload-time = "2026-05-18T19:19:01.28Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a1/5fcf7eb9904b80086aa47dcf0027de07b1bb990afad2e6823144c368ae04/lxml-6.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635", size = 5048232, upload-time = "2026-05-18T19:18:12.67Z" }, - { url = "https://files.pythonhosted.org/packages/77/74/1f601b63c7a69fcdf10fa9b148c81da8442204194f6c55509cc485c786b9/lxml-6.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf", size = 4777023, upload-time = "2026-05-18T19:18:15.928Z" }, - { url = "https://files.pythonhosted.org/packages/a2/b9/7a78f51aec95b1bf780d78e12705a9f6533284f8693dc5c0e6724fa53d3f/lxml-6.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc", size = 5645773, upload-time = "2026-05-18T19:18:23.223Z" }, - { url = "https://files.pythonhosted.org/packages/a5/6e/98a7b7ad54e4e74fa1f20fff776913980619d0ebe5558232d7da6580bdd8/lxml-6.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955", size = 5233088, upload-time = "2026-05-18T19:18:31.433Z" }, - { url = "https://files.pythonhosted.org/packages/65/d1/bc0ed2427bf609f2ee10da303a6a226f9c8bce94f945dc29a32ce55de6e4/lxml-6.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a", size = 5260995, upload-time = "2026-05-18T19:18:37.091Z" }, - { url = "https://files.pythonhosted.org/packages/69/8b/6772e1a4b513fc50a8d931f19edde0e13ae6918510a1e13ff67864f3e5ed/lxml-6.1.1-cp312-cp312-win32.whl", hash = "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a", size = 3596382, upload-time = "2026-05-18T19:17:18.37Z" }, - { url = "https://files.pythonhosted.org/packages/1b/89/45198e9624762af2dfd2cb8782598477ceb29f6e59caab560388ae1f4ec1/lxml-6.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77", size = 3997255, upload-time = "2026-05-18T19:17:56.781Z" }, - { url = "https://files.pythonhosted.org/packages/90/a9/7a54b6834088d9ae528a7b780584ba6a39a9457b0ac330479f20ffbc9449/lxml-6.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f", size = 3659610, upload-time = "2026-05-19T19:22:50.843Z" }, - { url = "https://files.pythonhosted.org/packages/a5/eb/7e6f37c5584ccbb2ff267f56fd0339016938c1c8684cfefab9b33ffc2f36/lxml-6.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736", size = 8559780, upload-time = "2026-05-18T19:17:57.661Z" }, - { url = "https://files.pythonhosted.org/packages/a1/36/587c2521cf23a2cd6c9c22108aa7528f683a1f195ed7ccd23a4b1786ad36/lxml-6.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9", size = 4618006, upload-time = "2026-05-18T19:18:04.452Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ca/ab7bfe2bf4c972af5e7878262845ead3a24a929a9b04bc11c7c1ece6c82a/lxml-6.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354", size = 4924139, upload-time = "2026-05-18T19:19:04.873Z" }, - { url = "https://files.pythonhosted.org/packages/6b/55/a0c72851dfee5ecc689f949723a73dea457758912542cb955b108eaf0d8f/lxml-6.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca", size = 5082329, upload-time = "2026-05-18T19:19:09.728Z" }, - { url = "https://files.pythonhosted.org/packages/f0/b6/0608f7d61a3b96cc67e5648a3d906e31a5082093e10e7be65b3886289938/lxml-6.1.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099", size = 4993564, upload-time = "2026-05-18T19:19:13.608Z" }, - { url = "https://files.pythonhosted.org/packages/4c/66/ae227524b066d29d55bf0b453d93d2d793c40218657d643dcbbca13b8faf/lxml-6.1.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6", size = 5613467, upload-time = "2026-05-18T19:19:16.228Z" }, - { url = "https://files.pythonhosted.org/packages/a6/76/dbe4a00b50385e40194231dcfe5a12c059de7cf90e89c83407d2b085b719/lxml-6.1.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085", size = 5228304, upload-time = "2026-05-18T19:19:19.354Z" }, - { url = "https://files.pythonhosted.org/packages/1c/01/00b1b8442ed2041793336868ba0b9ea4b13d7da7c085c6404c207a63bf79/lxml-6.1.1-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e", size = 5341607, upload-time = "2026-05-18T19:19:22.297Z" }, - { url = "https://files.pythonhosted.org/packages/63/36/1ad29931e9a4638bb707869f01d423a6c815f82152138d1a40dfcfde2b95/lxml-6.1.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f", size = 4700168, upload-time = "2026-05-18T19:19:25.133Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d1/a9536cecf9be18a0dc72d32bead283a2332d1ffebd2dd3ac70ce444686e5/lxml-6.1.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c", size = 5232487, upload-time = "2026-05-18T19:19:28.603Z" }, - { url = "https://files.pythonhosted.org/packages/0e/77/b4fb1e03bf5d130e879214d3100092e386418807fb74dd0adc4b0a48f351/lxml-6.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b", size = 5044231, upload-time = "2026-05-18T19:18:42.246Z" }, - { url = "https://files.pythonhosted.org/packages/26/4c/d00daeeb0a5530c4028a9232aa1b93db3ef4ed2158c116ea73c79a9765b3/lxml-6.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2", size = 4769450, upload-time = "2026-05-18T19:18:48.013Z" }, - { url = "https://files.pythonhosted.org/packages/ed/6a/715a3a8d156ce42f29cf014706f5410c2ff3b02267774110fc23266409fe/lxml-6.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5", size = 5635874, upload-time = "2026-05-18T19:18:51.914Z" }, - { url = "https://files.pythonhosted.org/packages/45/37/0544bc21dde2a88f3a17b504e6fc79c0e01d25a33c2f6079724e9e72b9c7/lxml-6.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785", size = 5223987, upload-time = "2026-05-18T19:18:59.715Z" }, - { url = "https://files.pythonhosted.org/packages/4d/f8/f6a5e8185bcb28c2befae3d31f8e3df3b811cb0f47746517a81279fcafe1/lxml-6.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947", size = 5250276, upload-time = "2026-05-18T19:19:03.834Z" }, - { url = "https://files.pythonhosted.org/packages/c7/f2/1a2b9f1b7a49d45495369be7ef9ad05b262930f2eab3e3145706fca8083f/lxml-6.1.1-cp313-cp313-win32.whl", hash = "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca", size = 3596903, upload-time = "2026-05-18T19:17:29.863Z" }, - { url = "https://files.pythonhosted.org/packages/e6/99/f4ffb024f238eec2131aaa09f3278fb6129cf892741bf68e1fc1afb8c100/lxml-6.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660", size = 3995869, upload-time = "2026-05-18T19:18:02.596Z" }, - { url = "https://files.pythonhosted.org/packages/d1/53/70eb8c5c6037f27448f1e3c54ebede9545a801ae63f0a7254afca4fe8e45/lxml-6.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc", size = 3658490, upload-time = "2026-05-19T19:22:53.846Z" }, - { url = "https://files.pythonhosted.org/packages/13/e2/2e325795566de01d0d7c3bb57d3c370616b2d07b01214e84eec5d3b10963/lxml-6.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0", size = 8577146, upload-time = "2026-05-18T19:18:17.765Z" }, - { url = "https://files.pythonhosted.org/packages/93/cf/5630b5e4be7d2e6bee8efe83865c925221103cf0221303b104ce134b01e2/lxml-6.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840", size = 4623866, upload-time = "2026-05-18T19:18:30.669Z" }, - { url = "https://files.pythonhosted.org/packages/d2/51/3904907c063451cf8d4a5c9fe0cad95fa1f4ec57f4e3884fa0731bd7a305/lxml-6.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14", size = 4950022, upload-time = "2026-05-18T19:19:31.958Z" }, - { url = "https://files.pythonhosted.org/packages/94/cd/9c7611a51c37a2830928405817cc5d56a97f64fab83cc3f628748b135749/lxml-6.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909", size = 5086695, upload-time = "2026-05-18T19:19:34.764Z" }, - { url = "https://files.pythonhosted.org/packages/da/d6/24e3b5906abb0b674ff2ae195bc3ce59708df2bcd17cf17703b2d7dd643a/lxml-6.1.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00", size = 5031642, upload-time = "2026-05-18T19:19:37.771Z" }, - { url = "https://files.pythonhosted.org/packages/2d/db/6ec54f99019838bff54785c51da07f189eb4676861c5f2730962b0d8d665/lxml-6.1.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955", size = 5647338, upload-time = "2026-05-18T19:19:40.553Z" }, - { url = "https://files.pythonhosted.org/packages/42/3d/ef4dcfffd22d27a61805d8ed9f7fb888495bc6aa88648fa07c1eaa5586b6/lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13", size = 5239528, upload-time = "2026-05-18T19:19:43.657Z" }, - { url = "https://files.pythonhosted.org/packages/62/bb/37fb3f0dff146bdcfa78eec47879273820b2a0bf350ec236ce14bd0b1c26/lxml-6.1.1-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7", size = 5350730, upload-time = "2026-05-18T19:19:46.307Z" }, - { url = "https://files.pythonhosted.org/packages/90/42/43253f168388df4fae1f38c01df36ddb9bee39e2048167b54cdcbae85ea3/lxml-6.1.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245", size = 4697530, upload-time = "2026-05-18T19:19:49.889Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a8/c5a8504f81bbdfc8e7094c2c850cdb4ed6777fc4d5ddd9e5ab819f3b0d54/lxml-6.1.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5", size = 5250670, upload-time = "2026-05-18T19:19:53.199Z" }, - { url = "https://files.pythonhosted.org/packages/77/b7/c7e76ab18744d75e21f320ebf9ff9d1ceae2b54dd431ea5a64caf26c9672/lxml-6.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462", size = 5084485, upload-time = "2026-05-18T19:19:08.422Z" }, - { url = "https://files.pythonhosted.org/packages/31/31/b35c53f8ef7b7c31cacd23d3638652fff7bcd1deb6eedb709ab43b685908/lxml-6.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465", size = 4737635, upload-time = "2026-05-18T19:19:12.321Z" }, - { url = "https://files.pythonhosted.org/packages/d9/06/31f23c813a7fe8e0cb1b175e915b08c9bf4e86d225b210feadbdbe519667/lxml-6.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a", size = 5670681, upload-time = "2026-05-18T19:19:15.001Z" }, - { url = "https://files.pythonhosted.org/packages/1a/bc/ce619bccc89b1fd9ad8a8e1330ee3f3beff9f2ff95b712d7bbcdd6e22fc3/lxml-6.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590", size = 5238229, upload-time = "2026-05-18T19:19:18.131Z" }, - { url = "https://files.pythonhosted.org/packages/2f/5d/b329acbbedc0b619ebc2be6cf7ee9ed07e80892c88d4dfd612c33805789a/lxml-6.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb", size = 5264191, upload-time = "2026-05-18T19:19:21.118Z" }, - { url = "https://files.pythonhosted.org/packages/d6/85/be36fb1425b30db3c3f9df75fe86343ebffb79e6320bd7f588e25bfeac39/lxml-6.1.1-cp314-cp314-win32.whl", hash = "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603", size = 3657202, upload-time = "2026-05-18T19:17:39.509Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ce/3cf9a827342269f54d405a6202397de63f07c69cbd6ce7d183a3f0cba1e9/lxml-6.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137", size = 4064497, upload-time = "2026-05-18T19:18:14.662Z" }, - { url = "https://files.pythonhosted.org/packages/d9/3e/1a957bde8f0760039e627f94699f82caa782c9d838d86c3d28245ee67212/lxml-6.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf", size = 3741991, upload-time = "2026-05-19T19:22:59.111Z" }, - { url = "https://files.pythonhosted.org/packages/78/b2/00ed55b3a2efa4658fb795c38d1090ec9b3e8a6c3683d4441fa517f09c3b/lxml-6.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee", size = 8827545, upload-time = "2026-05-18T19:18:41.193Z" }, - { url = "https://files.pythonhosted.org/packages/c0/73/74573db19baa618d5f266f2407898b087ff6927115b00b71e5fc1b700847/lxml-6.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c", size = 4735736, upload-time = "2026-05-18T19:18:46.761Z" }, - { url = "https://files.pythonhosted.org/packages/16/02/6f7061f4f95f51e545d48e87647c54791d204a4e881be4156e7a26ba5338/lxml-6.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef", size = 4970291, upload-time = "2026-05-18T19:19:56.215Z" }, - { url = "https://files.pythonhosted.org/packages/b0/02/55fc057d8283427dea7d6edb102e7a840239c77a64a983d92f62a304c0e9/lxml-6.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a", size = 5102822, upload-time = "2026-05-18T19:19:59.223Z" }, - { url = "https://files.pythonhosted.org/packages/e4/48/8e1cf78d89d66850121d9255a2a24414c98f775da93b90cf976956c24b14/lxml-6.1.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c", size = 5027923, upload-time = "2026-05-18T19:20:01.549Z" }, - { url = "https://files.pythonhosted.org/packages/ed/00/0632a0647612c8af24d26997b3b961397daa9d5b2581444805933629a4cb/lxml-6.1.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525", size = 5595843, upload-time = "2026-05-18T19:20:03.93Z" }, - { url = "https://files.pythonhosted.org/packages/bc/86/ab008a7dc360711b66858d61c80a5979a70a09f2aa2b05d9698df80b803d/lxml-6.1.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca", size = 5224515, upload-time = "2026-05-18T19:20:06.381Z" }, - { url = "https://files.pythonhosted.org/packages/75/c6/2702ff375e728e34f56d9a45339a9cf7e4427e917f542225242d63a05afa/lxml-6.1.1-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e", size = 5312511, upload-time = "2026-05-18T19:20:09.308Z" }, - { url = "https://files.pythonhosted.org/packages/b7/57/a5807c98f87a86f10ef9ffab35516df7c0f0c4b6d5d33e9f608ab9c04a31/lxml-6.1.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038", size = 4639206, upload-time = "2026-05-18T19:20:11.704Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e1/8a0a2c35734812395f4da4eaf33748a7e5705bfb2a58b128da764339d5ec/lxml-6.1.1-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e", size = 5232404, upload-time = "2026-05-18T19:20:14.064Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e2/0e6a4dd5ad84d01d99aa7bae7cfefd4a760a0e0f8176818241de17d9b6c0/lxml-6.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072", size = 5083769, upload-time = "2026-05-18T19:19:23.758Z" }, - { url = "https://files.pythonhosted.org/packages/a0/7e/161f33d463f6ffc1c7679104b65086dea120080d49dde4d238f015aaee2f/lxml-6.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52", size = 4758936, upload-time = "2026-05-18T19:19:27.256Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fb/2369825e3f6ca99305bf9f7b7085fda91c8b0922a89e54d900974aa3ef85/lxml-6.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b", size = 5620296, upload-time = "2026-05-18T19:19:29.993Z" }, - { url = "https://files.pythonhosted.org/packages/30/90/d61e383146f74c5ab683947ea14dc7b82778838ab9b95ea73a23b60d0191/lxml-6.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2", size = 5228598, upload-time = "2026-05-18T19:19:33.523Z" }, - { url = "https://files.pythonhosted.org/packages/76/2d/2dafd8149e94b05bb070690efd5bb2680720681e03ff03fc57d2b70a1105/lxml-6.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e", size = 5247845, upload-time = "2026-05-18T19:19:36.649Z" }, - { url = "https://files.pythonhosted.org/packages/ce/68/b30e913340c380ddac9580c6e6230991fc37240ec4f64704833e4f3e2769/lxml-6.1.1-cp314-cp314t-win32.whl", hash = "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1", size = 3897345, upload-time = "2026-05-18T19:17:33.562Z" }, - { url = "https://files.pythonhosted.org/packages/3c/4e/9eb2af5335545f9fbcd7af57bcf87c6025d31eaa31b14ec184a6c8675328/lxml-6.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e", size = 4393350, upload-time = "2026-05-18T19:18:10.076Z" }, - { url = "https://files.pythonhosted.org/packages/7f/2c/0f1e93c636720e8a3eb59af2bfda99d98b55891e1c53bc30c2e0e865f01b/lxml-6.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c", size = 3817223, upload-time = "2026-05-19T19:22:56.823Z" }, - { url = "https://files.pythonhosted.org/packages/b5/32/86a3f0f724a3a402d4627937a7fc27b160e45e7012b4adf47f6e1e844511/lxml-6.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e", size = 3930127, upload-time = "2026-05-18T19:19:02.27Z" }, - { url = "https://files.pythonhosted.org/packages/40/44/d832e82af08723761556d004b1d04d281c09f9a8cecd7d3148548c9941a3/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004", size = 4210769, upload-time = "2026-05-18T19:20:41.427Z" }, - { url = "https://files.pythonhosted.org/packages/6d/39/0dc5949f759ed7d951e0bb8c2f2d9d7aca1908d22352fa84a8afd2ea54af/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e", size = 4318163, upload-time = "2026-05-18T19:20:44.702Z" }, - { url = "https://files.pythonhosted.org/packages/e6/fb/8ab3845fe046ba4cbf74536bcf6801a774b7caf4350de1c5d37f1f0a9e90/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2", size = 4250945, upload-time = "2026-05-18T19:20:47.385Z" }, - { url = "https://files.pythonhosted.org/packages/68/1b/7553ab136894374ffae8851ec06f98f511cd8e66246e41b6be059d0a7289/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf", size = 4401664, upload-time = "2026-05-18T19:20:50.489Z" }, - { url = "https://files.pythonhosted.org/packages/db/a4/441aee36c6f6b249823d20fd91f9be9ab89d7c5a8ae542a4a4ca6d342d56/lxml-6.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84", size = 3508989, upload-time = "2026-05-18T19:18:38.158Z" }, -] - [[package]] name = "markdown-it-py" version = "4.2.0" @@ -2212,221 +1285,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] -[[package]] -name = "mmh3" -version = "5.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/1a/edb23803a168f070ded7a3014c6d706f63b90c84ccc024f89d794a3b7a6d/mmh3-5.2.1.tar.gz", hash = "sha256:bbea5b775f0ac84945191fb83f845a6fd9a21a03ea7f2e187defac7e401616ad", size = 33775, upload-time = "2026-03-05T15:55:57.716Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/d7/3312a59df3c1cdd783f4cf0c4ee8e9decff9c5466937182e4cc7dbbfe6c5/mmh3-5.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dae0f0bd7d30c0ad61b9a504e8e272cb8391eed3f1587edf933f4f6b33437450", size = 56082, upload-time = "2026-03-05T15:53:59.702Z" }, - { url = "https://files.pythonhosted.org/packages/61/96/6f617baa098ca0d2989bfec6d28b5719532cd8d8848782662f5b755f657f/mmh3-5.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9aeaf53eaa075dd63e81512522fd180097312fb2c9f476333309184285c49ce0", size = 40458, upload-time = "2026-03-05T15:54:01.548Z" }, - { 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/f6/09/a806334ce1d3d50bf782b95fcee8b3648e1e170327d4bb7b4bad2ad7d956/mmh3-5.2.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080c0637aea036f35507e803a4778f119a9b436617694ae1c5c366805f1e997", size = 97242, upload-time = "2026-03-05T15:54:04.536Z" }, - { 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/36/b5/613772c1c6ed5f7b63df55eb131e887cc43720fec392777b95a79d34e640/mmh3-5.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:915e7a2418f10bd1151b1953df06d896db9783c9cfdb9a8ee1f9b3a4331ab503", size = 98524, upload-time = "2026-03-05T15:54:13.122Z" }, - { 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/f1/b9/5e4cca8dcccf298add0a27f3c357bc8cf8baf821d35cdc6165e4bd5a48b0/mmh3-5.2.1-cp311-cp311-win32.whl", hash = "sha256:baeb47635cb33375dee4924cd93d7f5dcaa786c740b08423b0209b824a1ee728", size = 40751, upload-time = "2026-03-05T15:54:18.714Z" }, - { url = "https://files.pythonhosted.org/packages/72/fc/5b11d49247f499bcda591171e9cf3b6ee422b19e70aa2cef2e0ae65ca3b9/mmh3-5.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:1e4ecee40ba19e6975e1120829796770325841c2f153c0e9aecca927194c6a2a", size = 41517, upload-time = "2026-03-05T15:54:19.764Z" }, - { url = "https://files.pythonhosted.org/packages/8a/5f/2a511ee8a1c2a527c77726d5231685b72312c5a1a1b7639ad66a9652aa84/mmh3-5.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:c302245fd6c33d96bd169c7ccf2513c20f4c1e417c07ce9dce107c8bc3f8411f", size = 39287, upload-time = "2026-03-05T15:54:20.904Z" }, - { 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/f6/80/64a02cc3e95c3af0aaa2590849d9ed24a9f14bb93537addde688e039b7c3/mmh3-5.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4eda76074cfca2787c8cf1bec603eaebdddd8b061ad5502f85cddae998d54f00", size = 40500, upload-time = "2026-03-05T15:54:22.953Z" }, - { 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/59/c2/bf4537a8e58e21886ef16477041238cab5095c836496e19fafc34b7445d2/mmh3-5.2.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d0b7e803191db5f714d264044e06189c8ccd3219e936cc184f07106bd17fd7b", size = 97292, upload-time = "2026-03-05T15:54:25.335Z" }, - { 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/dd/38/99f7f75cd27d10d8b899a1caafb9d531f3903e4d54d572220e3d8ac35e89/mmh3-5.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62815d2c67f2dd1be76a253d88af4e1da19aeaa1820146dec52cf8bee2958b16", size = 98623, upload-time = "2026-03-05T15:54:33.801Z" }, - { 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/df/94/6ebb9094cfc7ac5e7950776b9d13a66bb4a34f83814f32ba2abc9494fc68/mmh3-5.2.1-cp312-cp312-win32.whl", hash = "sha256:7374d6e3ef72afe49697ecd683f3da12f4fc06af2d75433d0580c6746d2fa025", size = 40773, upload-time = "2026-03-05T15:54:40.077Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/cd3527198cf159495966551c84a5f36805a10ac17b294f41f67b83f6a4d6/mmh3-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9fed49c6ce4ed7e73f13182760c65c816da006debe67f37635580dfb0fae00", size = 41560, upload-time = "2026-03-05T15:54:41.148Z" }, - { url = "https://files.pythonhosted.org/packages/15/96/6fe5ebd0f970a076e3ed5512871ce7569447b962e96c125528a2f9724470/mmh3-5.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfcb95d9a744e6e2827dfc66ad10e1020e0cac255eb7f85652832d5a264c2fc", size = 39313, upload-time = "2026-03-05T15:54:42.171Z" }, - { url = "https://files.pythonhosted.org/packages/25/a5/9daa0508a1569a54130f6198d5462a92deda870043624aa3ea72721aa765/mmh3-5.2.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:723b2681ed4cc07d3401bbea9c201ad4f2a4ca6ba8cddaff6789f715dd2b391e", size = 40832, upload-time = "2026-03-05T15:54:43.212Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6b/3230c6d80c1f4b766dedf280a92c2241e99f87c1504ff74205ec8cebe451/mmh3-5.2.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:3619473a0e0d329fd4aec8075628f8f616be2da41605300696206d6f36920c3d", size = 41964, upload-time = "2026-03-05T15:54:44.204Z" }, - { 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" }, - { url = "https://files.pythonhosted.org/packages/2f/ed/6f88dda0df67de1612f2e130ffea34cf84aaee5bff5b0aff4dbff2babe34/mmh3-5.2.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:17fbb47f0885ace8327ce1235d0416dc86a211dcd8cc1e703f41523be32cfec8", size = 40330, upload-time = "2026-03-05T15:54:47.864Z" }, - { url = "https://files.pythonhosted.org/packages/3d/66/7516d23f53cdf90f43fce24ab80c28f45e6851d78b46bef8c02084edf583/mmh3-5.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d51fde50a77f81330523562e3c2734ffdca9c4c9e9d355478117905e1cfe16c6", size = 56078, upload-time = "2026-03-05T15:54:48.9Z" }, - { url = "https://files.pythonhosted.org/packages/bc/34/4d152fdf4a91a132cb226b671f11c6b796eada9ab78080fb5ce1e95adaab/mmh3-5.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:19bbd3b841174ae6ed588536ab5e1b1fe83d046e668602c20266547298d939a9", size = 40498, upload-time = "2026-03-05T15:54:49.942Z" }, - { 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/8b/f2/966ea560e32578d453c9e9db53d602cbb1d0da27317e232afa7c38ceba11/mmh3-5.2.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fd96476f04db5ceba1cfa0f21228f67c1f7402296f0e73fee3513aa680ad237b", size = 97320, upload-time = "2026-03-05T15:54:52.072Z" }, - { 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/54/f7/6b16eb1b40ee89bb740698735574536bc20d6cdafc65ae702ea235578e05/mmh3-5.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e4e1f580033335c6f76d1e0d6b56baf009d1a64d6a4816347e4271ba951f46d", size = 98686, upload-time = "2026-03-05T15:55:00.078Z" }, - { 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" }, - { url = "https://files.pythonhosted.org/packages/4b/f9/dc3787ee5c813cc27fe79f45ad4500d9b5437f23a7402435cc34e07c7718/mmh3-5.2.1-cp313-cp313-win32.whl", hash = "sha256:54b64fb2433bc71488e7a449603bf8bd31fbcf9cb56fbe1eb6d459e90b86c37b", size = 40769, upload-time = "2026-03-05T15:55:05.277Z" }, - { url = "https://files.pythonhosted.org/packages/43/67/850e0b5a1e97799822ebfc4ca0e8c6ece3ed8baf7dcdf64de817dfdda2ca/mmh3-5.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:cae6383181f1e345317742d2ddd88f9e7d2682fa4c9432e3a74e47d92dce0229", size = 41563, upload-time = "2026-03-05T15:55:06.283Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cc/98c90b28e1da5458e19fbfaf4adb5289208d3bfccd45dd14eab216a2f0bb/mmh3-5.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:022aa1a528604e6c83d0a7705fdef0b5355d897a9e0fa3a8d26709ceaa06965d", size = 39310, upload-time = "2026-03-05T15:55:07.323Z" }, - { url = "https://files.pythonhosted.org/packages/63/b4/65bc1fb2bb7f83e91c30865023b1847cf89a5f237165575e8c83aa536584/mmh3-5.2.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:d771f085fcdf4035786adfb1d8db026df1eb4b41dac1c3d070d1e49512843227", size = 40794, upload-time = "2026-03-05T15:55:09.773Z" }, - { url = "https://files.pythonhosted.org/packages/c4/86/7168b3d83be8eb553897b1fac9da8bbb06568e5cfe555ffc329ebb46f59d/mmh3-5.2.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:7f196cd7910d71e9d9860da0ff7a77f64d22c1ad931f1dd18559a06e03109fc0", size = 41923, upload-time = "2026-03-05T15:55:10.924Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9b/b653ab611c9060ce8ff0ba25c0226757755725e789292f3ca138a58082cd/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b1f12bd684887a0a5d55e6363ca87056f361e45451105012d329b86ec19dbe0b", size = 39131, upload-time = "2026-03-05T15:55:11.961Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b4/5a2e0d34ab4d33543f01121e832395ea510132ea8e52cdf63926d9d81754/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d106493a60dcb4aef35a0fac85105e150a11cf8bc2b0d388f5a33272d756c966", size = 39825, upload-time = "2026-03-05T15:55:13.013Z" }, - { url = "https://files.pythonhosted.org/packages/bd/69/81699a8f39a3f8d368bec6443435c0c392df0d200ad915bf0d222b588e03/mmh3-5.2.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44983e45310ee5b9f73397350251cdf6e63a466406a105f1d16cb5baa659270b", size = 40344, upload-time = "2026-03-05T15:55:14.026Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b3/71c8c775807606e8fd8acc5c69016e1caf3200d50b50b6dd4b40ce10b76c/mmh3-5.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:368625fb01666655985391dbad3860dc0ba7c0d6b9125819f3121ee7292b4ac8", size = 56291, upload-time = "2026-03-05T15:55:15.137Z" }, - { url = "https://files.pythonhosted.org/packages/6f/75/2c24517d4b2ce9e4917362d24f274d3d541346af764430249ddcc4cb3a08/mmh3-5.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:72d1cc63bcc91e14933f77d51b3df899d6a07d184ec515ea7f56bff659e124d7", size = 40575, upload-time = "2026-03-05T15:55:16.518Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b9/e4a360164365ac9f07a25f0f7928e3a66eb9ecc989384060747aa170e6aa/mmh3-5.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e8b4b5580280b9265af3e0409974fb79c64cf7523632d03fbf11df18f8b0181e", size = 40052, upload-time = "2026-03-05T15:55:17.735Z" }, - { url = "https://files.pythonhosted.org/packages/97/ca/120d92223a7546131bbbc31c9174168ee7a73b1366f5463ffe69d9e691fe/mmh3-5.2.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4cbbde66f1183db040daede83dd86c06d663c5bb2af6de1142b7c8c37923dd74", size = 97311, upload-time = "2026-03-05T15:55:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/b6/71/c1a60c1652b8813ef9de6d289784847355417ee0f2980bca002fe87f4ae5/mmh3-5.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8ff038d52ef6aa0f309feeba00c5095c9118d0abf787e8e8454d6048db2037fc", size = 103279, upload-time = "2026-03-05T15:55:20.448Z" }, - { url = "https://files.pythonhosted.org/packages/48/29/ad97f4be1509cdcb28ae32c15593ce7c415db47ace37f8fad35b493faa9a/mmh3-5.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4130d0b9ce5fad6af07421b1aecc7e079519f70d6c05729ab871794eded8617", size = 106290, upload-time = "2026-03-05T15:55:21.6Z" }, - { url = "https://files.pythonhosted.org/packages/77/29/1f86d22e281bd8827ba373600a4a8b0c0eae5ca6aa55b9a8c26d2a34decc/mmh3-5.2.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e0bfe77d238308839699944164b96a2eeccaf55f2af400f54dc20669d8d5f2", size = 113116, upload-time = "2026-03-05T15:55:22.826Z" }, - { url = "https://files.pythonhosted.org/packages/a7/7c/339971ea7ed4c12d98f421f13db3ea576a9114082ccb59d2d1a0f00ccac1/mmh3-5.2.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f963eafc0a77a6c0562397da004f5876a9bcf7265a7bcc3205e29636bc4a1312", size = 120740, upload-time = "2026-03-05T15:55:24.3Z" }, - { url = "https://files.pythonhosted.org/packages/e4/92/3c7c4bdb8e926bb3c972d1e2907d77960c1c4b250b41e8366cf20c6e4373/mmh3-5.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92883836caf50d5255be03d988d75bc93e3f86ba247b7ca137347c323f731deb", size = 99143, upload-time = "2026-03-05T15:55:25.456Z" }, - { url = "https://files.pythonhosted.org/packages/df/0a/33dd8706e732458c8375eae63c981292de07a406bad4ec03e5269654aa2c/mmh3-5.2.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57b52603e89355ff318025dd55158f6e71396c0f1f609d548e9ea9c94cc6ce0a", size = 98703, upload-time = "2026-03-05T15:55:26.723Z" }, - { url = "https://files.pythonhosted.org/packages/51/04/76bbce05df76cbc3d396f13b2ea5b1578ef02b6a5187e132c6c33f99d596/mmh3-5.2.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f40a95186a72fa0b67d15fef0f157bfcda00b4f59c8a07cbe5530d41ac35d105", size = 106484, upload-time = "2026-03-05T15:55:28.214Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8f/c6e204a2c70b719c1f62ffd9da27aef2dddcba875ea9c31ca0e87b975a46/mmh3-5.2.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:58370d05d033ee97224c81263af123dea3d931025030fd34b61227a768a8858a", size = 110012, upload-time = "2026-03-05T15:55:29.532Z" }, - { url = "https://files.pythonhosted.org/packages/e3/37/7181efd8e39db386c1ebc3e6b7d1f702a09d7c1197a6f2742ed6b5c16597/mmh3-5.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7be6dfb49e48fd0a7d91ff758a2b51336f1cd21f9d44b20f6801f072bd080cdd", size = 97508, upload-time = "2026-03-05T15:55:31.01Z" }, - { url = "https://files.pythonhosted.org/packages/42/0f/afa7ca2615fd85e1469474bb860e381443d0b868c083b62b41cb1d7ca32f/mmh3-5.2.1-cp314-cp314-win32.whl", hash = "sha256:54fe8518abe06a4c3852754bfd498b30cc58e667f376c513eac89a244ce781a4", size = 41387, upload-time = "2026-03-05T15:55:32.403Z" }, - { url = "https://files.pythonhosted.org/packages/71/0d/46d42a260ee1357db3d486e6c7a692e303c017968e14865e00efa10d09fc/mmh3-5.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3f796b535008708846044c43302719c6956f39ca2d93f2edda5319e79a29efbb", size = 42101, upload-time = "2026-03-05T15:55:33.646Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7b/848a8378059d96501a41159fca90d6a99e89736b0afbe8e8edffeac8c74b/mmh3-5.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:cd471ede0d802dd936b6fab28188302b2d497f68436025857ca72cd3810423fe", size = 39836, upload-time = "2026-03-05T15:55:35.026Z" }, - { url = "https://files.pythonhosted.org/packages/27/61/1dabea76c011ba8547c25d30c91c0ec22544487a8750997a27a0c9e1180b/mmh3-5.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5174a697ce042fa77c407e05efe41e03aa56dae9ec67388055820fb48cf4c3ba", size = 57727, upload-time = "2026-03-05T15:55:36.162Z" }, - { url = "https://files.pythonhosted.org/packages/b7/32/731185950d1cf2d5e28979cc8593016ba1619a295faba10dda664a4931b5/mmh3-5.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0a3984146e414684a6be2862d84fcb1035f4984851cb81b26d933bab6119bf00", size = 41308, upload-time = "2026-03-05T15:55:37.254Z" }, - { url = "https://files.pythonhosted.org/packages/76/aa/66c76801c24b8c9418b4edde9b5e57c75e72c94e29c48f707e3962534f18/mmh3-5.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bd6e7d363aa93bd3421b30b6af97064daf47bc96005bddba67c5ffbc6df426b8", size = 40758, upload-time = "2026-03-05T15:55:38.61Z" }, - { url = "https://files.pythonhosted.org/packages/9e/bb/79a1f638a02f0ae389f706d13891e2fbf7d8c0a22ecde67ba828951bb60a/mmh3-5.2.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:113f78e7463a36dbbcea05bfe688efd7fa759d0f0c56e73c974d60dcfec3dfcc", size = 109670, upload-time = "2026-03-05T15:55:40.13Z" }, - { url = "https://files.pythonhosted.org/packages/26/94/8cd0e187a288985bcfc79bf5144d1d712df9dee74365f59d26e3a1865be6/mmh3-5.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e8ec5f606e0809426d2440e0683509fb605a8820a21ebd120dcdba61b74ef7f", size = 117399, upload-time = "2026-03-05T15:55:42.076Z" }, - { url = "https://files.pythonhosted.org/packages/42/94/dfea6059bd5c5beda565f58a4096e43f4858fb6d2862806b8bbd12cbb284/mmh3-5.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22b0f9971ec4e07e8223f2beebe96a6cfc779d940b6f27d26604040dd74d3a44", size = 120386, upload-time = "2026-03-05T15:55:43.481Z" }, - { url = "https://files.pythonhosted.org/packages/47/cb/f9c45e62aaa67220179f487772461d891bb582bb2f9783c944832c60efd9/mmh3-5.2.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85ffc9920ffc39c5eee1e3ac9100c913a0973996fbad5111f939bbda49204bb7", size = 125924, upload-time = "2026-03-05T15:55:44.638Z" }, - { url = "https://files.pythonhosted.org/packages/a5/83/fe54a4a7c11bc9f623dfc1707decd034245602b076dfc1dcc771a4163170/mmh3-5.2.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aec798c2b01aaa65a55f1124f3405804184373abb318a3091325aece235f67c", size = 135280, upload-time = "2026-03-05T15:55:45.866Z" }, - { url = "https://files.pythonhosted.org/packages/97/67/fe7e9e9c143daddd210cd22aef89cbc425d58ecf238d2b7d9eb0da974105/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55dbbd8ffbc40d1697d5e2d0375b08599dae8746b0b08dea05eee4ce81648fac", size = 110050, upload-time = "2026-03-05T15:55:47.074Z" }, - { url = "https://files.pythonhosted.org/packages/43/c4/6d4b09fcbef80794de447c9378e39eefc047156b290fa3dd2d5257ca8227/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6c85c38a279ca9295a69b9b088a2e48aa49737bb1b34e6a9dc6297c110e8d912", size = 111158, upload-time = "2026-03-05T15:55:48.239Z" }, - { url = "https://files.pythonhosted.org/packages/81/a6/ca51c864bdb30524beb055a6d8826db3906af0834ec8c41d097a6e8573d5/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6290289fa5fb4c70fd7f72016e03633d60388185483ff3b162912c81205ae2cf", size = 116890, upload-time = "2026-03-05T15:55:49.405Z" }, - { url = "https://files.pythonhosted.org/packages/cc/04/5a1fe2e2ad843d03e89af25238cbc4f6840a8bb6c4329a98ab694c71deda/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4fc6cd65dc4d2fdb2625e288939a3566e36127a84811a4913f02f3d5931da52d", size = 123121, upload-time = "2026-03-05T15:55:50.61Z" }, - { url = "https://files.pythonhosted.org/packages/af/4d/3c820c6f4897afd25905270a9f2330a23f77a207ea7356f7aadace7273c0/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:623f938f6a039536cc02b7582a07a080f13fdfd48f87e63201d92d7e34d09a18", size = 110187, upload-time = "2026-03-05T15:55:52.143Z" }, - { url = "https://files.pythonhosted.org/packages/21/54/1d71cd143752361c0aebef16ad3f55926a6faf7b112d355745c1f8a25f7f/mmh3-5.2.1-cp314-cp314t-win32.whl", hash = "sha256:29bc3973676ae334412efdd367fcd11d036b7be3efc1ce2407ef8676dabfeb82", size = 41934, upload-time = "2026-03-05T15:55:53.564Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e4/63a2a88f31d93dea03947cccc2a076946857e799ea4f7acdecbf43b324aa/mmh3-5.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:28cfab66577000b9505a0d068c731aee7ca85cd26d4d63881fab17857e0fe1fb", size = 43036, upload-time = "2026-03-05T15:55:55.252Z" }, - { url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391, upload-time = "2026-03-05T15:55:56.697Z" }, -] - -[[package]] -name = "multidict" -version = "6.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } -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/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, - { 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/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, - { 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/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, - { 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/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, - { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, - { 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/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, - { 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/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, - { 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/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, - { 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/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, - { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, - { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, - { 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/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, - { 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/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, - { 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/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, - { 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/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, - { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, - { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, - { 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/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, - { 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/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, - { 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/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, - { 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/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, - { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, - { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, - { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, - { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, - { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, - { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, - { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, - { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, - { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, - { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, - { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, - { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, - { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, - { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, - { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, - { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, - { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, - { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, - { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, - { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, - { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, - { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, - { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, - { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, - { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, - { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, - { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, - { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, - { 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" }, -] - [[package]] name = "mypy" version = "2.1.0" @@ -2504,228 +1362,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/09/dc/f3dfb7488b770f3f67e6545085bf2abea5172e88f57b8ad25ef860ca704c/myst_parser-5.1.0-py3-none-any.whl", hash = "sha256:9c91c52b3cdb4d94a6506e4fab4e2f296c7623a0da0dcbe6de1565c3dad67a8a", size = 85817, upload-time = "2026-05-13T09:38:17.904Z" }, ] -[[package]] -name = "numpy" -version = "2.4.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.12' and sys_platform == 'win32'", - "python_full_version < '3.12' and sys_platform != 'win32'", -] -sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, - { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, - { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, - { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, - { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, - { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, - { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, - { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, - { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, - { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, - { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, - { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, - { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, - { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, - { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, - { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, - { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, - { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, - { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, - { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, - { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, - { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, - { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, - { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, - { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, - { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, - { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, - { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, - { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, - { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, - { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, - { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, - { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, - { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, - { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, - { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, - { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, - { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, - { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, - { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, - { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, - { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, - { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, - { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, - { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, - { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, - { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, - { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, - { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, - { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, - { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, - { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, - { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, - { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, - { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, -] - -[[package]] -name = "numpy" -version = "2.5.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform != 'win32'", - "python_full_version == '3.14.*' and sys_platform != 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform != 'win32'", - "python_full_version == '3.12.*' and sys_platform != 'win32'", -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, - { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, - { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, - { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, - { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, - { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, - { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, - { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, - { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, - { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, - { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, - { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, - { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, - { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, - { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, - { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, - { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, - { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, - { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, - { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, - { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, - { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, - { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, - { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, - { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, - { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, - { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, - { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, - { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, - { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, - { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, - { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, - { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, - { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, -] - -[[package]] -name = "oauthlib" -version = "3.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, -] - -[[package]] -name = "onnxruntime" -version = "1.27.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "flatbuffers" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "packaging" }, - { name = "protobuf" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/e4/5353d7e09ced4a8f473f843223fc75d726b2b5519dcefc12f22a6c92852d/onnxruntime-1.27.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:8ba14a38c570087f3cdb8cfba33f7a38a1e826c1e5b29e17c28ceda0cc910016", size = 18416484, upload-time = "2026-06-15T22:43:43.894Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1f/a2117aa3f144fce88774efa37440d0ca72d0c9144854dfc0961f2b04c6fc/onnxruntime-1.27.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2eb083321af8a236a84c7c140a7f4cecbfa2a987a18c07c78db471c20cd390ef", size = 16419330, upload-time = "2026-06-15T22:42:37.58Z" }, - { url = "https://files.pythonhosted.org/packages/e0/cd/74bb804170ceb622fda9111df31a07b3024f7491472256d3a90b5391a4d2/onnxruntime-1.27.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4f7b0e90d2d212e2c2deaa6c8291616183ab815d3ec558ea12d3ac8b26d36f4", size = 18636930, upload-time = "2026-06-15T22:43:01.584Z" }, - { url = "https://files.pythonhosted.org/packages/fe/8f/5b8e2b85e81735696887175dbaf6409f215683f5ca9d4928fbb038211d32/onnxruntime-1.27.0-cp311-cp311-win_amd64.whl", hash = "sha256:ff050e4f6bf7f12918fa14dcb047c0b02e295f35e86d42532552be4b3d54e977", size = 13356110, upload-time = "2026-06-15T22:43:32.172Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3a/4f568de678126b6a371a93862f015a82138359decd97fcac61fc84b5b774/onnxruntime-1.27.0-cp311-cp311-win_arm64.whl", hash = "sha256:75fbc1e1fb43a39a856c8209c544cca7817b5de7ac16b15b1bdf55d1cc67b9df", size = 13098635, upload-time = "2026-06-15T22:43:19.607Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b7/dd3a524ed93a820dff1af902d0412957ab12499953333e9daa01af5bc480/onnxruntime-1.27.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a14c2ce45312def86b77aea651f46565e45960cf5f0721bfdff449165086ab76", size = 18433506, upload-time = "2026-06-15T22:43:47.026Z" }, - { url = "https://files.pythonhosted.org/packages/84/86/c3b6b17745a1997d784dadc9bd88d713d2e6721139a5a0e885b28cfb79b1/onnxruntime-1.27.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6fddce0539a4898c7bef35b052ffd37935b2190e35488eab99ce91887743ea1", size = 16438140, upload-time = "2026-06-15T22:42:40.666Z" }, - { url = "https://files.pythonhosted.org/packages/26/81/24dd9b31b0fb912ee19ca53ac1c9764bfd79d58a2ccef564eb693be831a5/onnxruntime-1.27.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c65a7438632d55dfbc8a02ee60bd6cf7dd9d1ba05a43d4b851452f32338e194", size = 18658316, upload-time = "2026-06-15T22:43:04.012Z" }, - { url = "https://files.pythonhosted.org/packages/4f/88/8ec9db1a4d126bb8b758992beb40d1249df171917d75f44a327eb5f20dda/onnxruntime-1.27.0-cp312-cp312-win_amd64.whl", hash = "sha256:20c321cf187ba496e648acf6b4cf90b4d398b0d17c2a77fdaeba365b908cc1c1", size = 13358769, upload-time = "2026-06-15T22:43:34.581Z" }, - { url = "https://files.pythonhosted.org/packages/ae/9f/fdad359dfcba7e7cd8815569b304a596531d4efa77a75d77f8b4981891a2/onnxruntime-1.27.0-cp312-cp312-win_arm64.whl", hash = "sha256:d0d1f68868e2ef30ef70998ba9bbbc5c305e9b17041e3936751c1b8aa6aade06", size = 13104440, upload-time = "2026-06-15T22:43:22.893Z" }, - { url = "https://files.pythonhosted.org/packages/fb/2b/54208fd03ad410480bc17edf4869376362da8bbf46fe186ddf4cb5cc20fe/onnxruntime-1.27.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:b3e5b58b8c89c2b20e086e890aa9527377e5c240dc3ecc1640d18e07705eeb1c", size = 18432958, upload-time = "2026-06-15T22:42:53.105Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/24fc51fcbb126da6d032372314e47b55c3faad58f2aa78c0e199ccd20b9c/onnxruntime-1.27.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48b3d87eb560ff6a772240506f3c78d6d27c63cafedd5c775672e1194f968cfd", size = 16438180, upload-time = "2026-06-15T22:42:43.093Z" }, - { url = "https://files.pythonhosted.org/packages/cb/19/14929c3c2fe0b79b41cce24463062bf3afa4cdd3c19dccf00319caa92bff/onnxruntime-1.27.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6872443f236a554921cda6f318c900e2d0c226792cf3534d00e5057c6926e5d2", size = 18658445, upload-time = "2026-06-15T22:43:08.053Z" }, - { url = "https://files.pythonhosted.org/packages/7f/76/59ed932b0244acd7bbbd6449480053a6d958ea66357f022f932872e19287/onnxruntime-1.27.0-cp313-cp313-win_amd64.whl", hash = "sha256:760021bca514d64a811837820d351a08a41741f16f8b4c26450da708fecf14e6", size = 13357856, upload-time = "2026-06-15T22:43:37.315Z" }, - { url = "https://files.pythonhosted.org/packages/79/51/d1ec60ec7b1e2ae2d7340ba52b8a13529140039cd4407ba8dddbbc046582/onnxruntime-1.27.0-cp313-cp313-win_arm64.whl", hash = "sha256:2fdfa9df40a0ded0028ce6f9cd863264237f3970559dea2b81456e9ac4622b94", size = 13104412, upload-time = "2026-06-15T22:43:27.457Z" }, - { url = "https://files.pythonhosted.org/packages/5e/7d/e6bb1c6445c94f708c38cd8fbb7bf0264108c33498b9445c93e60fe6d329/onnxruntime-1.27.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54c0c4e9202c36c4ecdb1f3443f5dfbfd5ee3b54d1362c4b4c6134110e74fb32", size = 16443331, upload-time = "2026-06-15T22:42:45.649Z" }, - { url = "https://files.pythonhosted.org/packages/72/1b/b18b31e806eabc41077810199fbbb36fbc2d5f19912416e5ccfbf73053d1/onnxruntime-1.27.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b215aa662c8f983f7d6dedafe65a9be72c26e5338e0fe98b3e0422c32c85428", size = 18670967, upload-time = "2026-06-15T22:43:10.621Z" }, - { url = "https://files.pythonhosted.org/packages/3a/37/48ab79c39b58a7c9f6f5aac1fa0ff2b993eb2643393d6ed9e839ddb6f347/onnxruntime-1.27.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0874edc171f470fc4dd2bbb60bc0989612ed1a8b89b365cda016630a93227f13", size = 18433941, upload-time = "2026-06-15T22:42:58.867Z" }, - { url = "https://files.pythonhosted.org/packages/6e/24/d535ca8a09dbf697f853377c8dc0820dbcaae5f334316b400b953afbcba8/onnxruntime-1.27.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b51c014cf1a4fcd93c29a97eac8071fa27710dae05a4d0380bb60a66d60a62c", size = 16439970, upload-time = "2026-06-15T22:42:48.023Z" }, - { url = "https://files.pythonhosted.org/packages/f9/b1/ea9ee80c0bdaa4efb13f29f8c236f3740f6655e8c092a2d119515a5a652c/onnxruntime-1.27.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:445fb702ea5241ba813a3ce2febe2e9408a64f6ad2eb610924322c536165f7cd", size = 18659240, upload-time = "2026-06-15T22:43:13.165Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f2/1404507d76a21940e8bf46f414e3d1abd94dc888cb89a30f4a540275846f/onnxruntime-1.27.0-cp314-cp314-win_amd64.whl", hash = "sha256:49e416be0d717338b6d041b99911b716d70c397d277056450724f93bdded3fc2", size = 13685306, upload-time = "2026-06-15T22:43:40.416Z" }, - { url = "https://files.pythonhosted.org/packages/10/e5/ca5cf012ccccb806c70e94aadfebca5606acc62b33eb88cec13352d0778f/onnxruntime-1.27.0-cp314-cp314-win_arm64.whl", hash = "sha256:856032937dd3bc7a7c141909c8d7ae4fde3e3f59bddf061ae627b9a051bda95c", size = 13456280, upload-time = "2026-06-15T22:43:29.693Z" }, - { url = "https://files.pythonhosted.org/packages/67/7b/dca330a8397e9d816c976d7aed4e24a4a2d279bb1e551e3d0221d1389b1d/onnxruntime-1.27.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6197a02e3f620c4dc13cff51b80672409fc1ffab3aa2593911b19fd322ff48b", size = 16443274, upload-time = "2026-06-15T22:42:50.467Z" }, - { url = "https://files.pythonhosted.org/packages/b7/f6/2bac21f722aa45d876d4a51f26bd0ef30e704068a3cd5021a5a7cd784271/onnxruntime-1.27.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:370d211e1ceeac4cd5f45301655463ac59e27cdc74d9f7aeb2d19ff4b7a76715", size = 18670781, upload-time = "2026-06-15T22:43:17.151Z" }, -] - -[[package]] -name = "openai" -version = "2.44.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/49/f5/7c7cb955305cb41f7f3c5fd7e0e38bf6bbf2658468863d4b7b868a5cb8df/openai-2.44.0.tar.gz", hash = "sha256:68a5a5ffad82b8ff7d451c437529fb64f7c3b8123aaf0c021966a882d9e3947d", size = 988753, upload-time = "2026-06-24T20:56:02.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/f4/561ed79fd94876160018a5e75254cfcb9b0e62d4dded9dcb20072e86d623/openai-2.44.0-py3-none-any.whl", hash = "sha256:0a2a3ab2e29aeda368700f662ff9ba0f9df17ba4c54577a64e08b8115a3cc0ad", size = 1366216, upload-time = "2026-06-24T20:55:58.882Z" }, -] - -[[package]] -name = "openpyxl" -version = "3.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "et-xmlfile" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, -] - [[package]] name = "opentelemetry-api" version = "1.43.0" @@ -2738,54 +1374,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/83/6dba32b85f31868400440dc7ad2ca1eab94cbbf3a7b0459ed39f8311a9e2/opentelemetry_api-1.43.0-py3-none-any.whl", hash = "sha256:20acf45e9b21851926835292e4045d290acade1edd2ff3de86d2f069687ba1fd", size = 61912, upload-time = "2026-06-24T15:19:35.434Z" }, ] -[[package]] -name = "opentelemetry-exporter-otlp-proto-common" -version = "1.43.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-proto" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/c1/e8098490ab15abf116dcaf9fa89ededcb35547c7d08d4b5a62f573dc1e63/opentelemetry_exporter_otlp_proto_common-1.43.0.tar.gz", hash = "sha256:c4e32ba6d6b13bdb2b8f6764c4fd28d00192826561aa04f6d14eedfce7ac076f", size = 20197, upload-time = "2026-06-24T15:20:00.247Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/b2/41ebc74ae1d5859901f1b69305de58724bf043381103d6ef413521cbc35a/opentelemetry_exporter_otlp_proto_common-1.43.0-py3-none-any.whl", hash = "sha256:123c3f9cc87218562490c63b36f497bf3a722faf174a515d1443f31ababa6264", size = 17048, upload-time = "2026-06-24T15:19:41.264Z" }, -] - -[[package]] -name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.43.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/e2/1d/6336453716ca0a240d4417d19e6d5b77a5e7163e5670ec4f7ec4d3ede7bf/opentelemetry_exporter_otlp_proto_grpc-1.43.0.tar.gz", hash = "sha256:1b3e0627daa9bc21884d4a13946807c255eb558bfe5bdd543dffb6f4c9faee0d", size = 27213, upload-time = "2026-06-24T15:20:00.907Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/74/2700b5d5c946bf2dba87073fce3dfc198c46bc92ea3d5693f54bc51c90b1/opentelemetry_exporter_otlp_proto_grpc-1.43.0-py3-none-any.whl", hash = "sha256:6a10d1feacffffda19acacbf277b736094b1e2f4dbb98c90ccb2c6e1962e2ec6", size = 19626, upload-time = "2026-06-24T15:19:42.233Z" }, -] - -[[package]] -name = "opentelemetry-exporter-otlp-proto-http" -version = "1.43.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/fc/92/0b9f56412483a8891d4843890294796c9df8ab42417bd9bad8035d840cb3/opentelemetry_exporter_otlp_proto_http-1.43.0.tar.gz", hash = "sha256:fa8a42bb7d00ee5391f4c0b04d8e6a46c03caa437903296ab73a81dc11ba118f", size = 25406, upload-time = "2026-06-24T15:20:01.515Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/20/b685ed7af2e17c29ffc8af56f1fa8bc2033258fc30fb0d2b722f49d13ba0/opentelemetry_exporter_otlp_proto_http-1.43.0-py3-none-any.whl", hash = "sha256:647f603aa8efdbdb4dbff842e0729d0406a6fff26b295a72d3d60e7d963b2610", size = 21795, upload-time = "2026-06-24T15:19:43.164Z" }, -] - [[package]] name = "opentelemetry-instrumentation" version = "0.64b0" @@ -2815,18 +1403,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/b3/8838d5f3db35b3854516ccfbf8ec26138d40388d828407182c890c3aa9d0/opentelemetry_instrumentation_threading-0.64b0-py3-none-any.whl", hash = "sha256:a285ffa750a958f7d368e947f5679a0214d588242cbffba0f5934cb02e9a17f4", size = 8487, upload-time = "2026-06-24T15:19:00.813Z" }, ] -[[package]] -name = "opentelemetry-proto" -version = "1.43.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "protobuf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e0/b9/d357faefb40bda1d4799913e6af611171ff22a2dedcb93576bc92242d056/opentelemetry_proto-1.43.0.tar.gz", hash = "sha256:224778df17e1f3fafeaaa21d874236ca5f6ffc2f86e0899298ec7351aac27924", size = 46481, upload-time = "2026-06-24T15:20:07.625Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/a7/3e5308cf548b8f72529c7db1afdb3a404211982376a12927fd7759f77bf3/opentelemetry_proto-1.43.0-py3-none-any.whl", hash = "sha256:c58f1f7ef84bc7dc2834016c0c37fe0081dde7ca9f6339be1970fbf9cdaaa90d", size = 72489, upload-time = "2026-06-24T15:19:51.164Z" }, -] - [[package]] name = "opentelemetry-sdk" version = "1.43.0" @@ -2970,15 +1546,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232, upload-time = "2026-01-18T20:55:45.448Z" }, ] -[[package]] -name = "overrides" -version = "7.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, -] - [[package]] name = "packaging" version = "26.2" @@ -2997,120 +1564,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] -[[package]] -name = "pdfminer-six" -version = "20260107" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "charset-normalizer" }, - { name = "cryptography" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/a4/5cec1112009f0439a5ca6afa8ace321f0ab2f48da3255b7a1c8953014670/pdfminer_six-20260107.tar.gz", hash = "sha256:96bfd431e3577a55a0efd25676968ca4ce8fd5b53f14565f85716ff363889602", size = 8512094, upload-time = "2026-01-07T13:29:12.937Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/8b/28c4eaec9d6b036a52cb44720408f26b1a143ca9bce76cc19e8f5de00ab4/pdfminer_six-20260107-py3-none-any.whl", hash = "sha256:366585ba97e80dffa8f00cebe303d2f381884d8637af4ce422f1df3ef38111a9", size = 6592252, upload-time = "2026-01-07T13:29:10.742Z" }, -] - -[[package]] -name = "pdfplumber" -version = "0.11.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pdfminer-six" }, - { name = "pillow" }, - { name = "pypdfium2" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/05/56/6f450312ba05a27d7713b73857c1a25100dbda04fbc1331b13fb227a607d/pdfplumber-0.11.10.tar.gz", hash = "sha256:b95b2d28c66efb0a794a83b88c6c6aea5987532a445d20a1cbcfa657022e6e57", size = 102892, upload-time = "2026-06-15T03:31:31.035Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/9a/07d658e1e7fad860f1c541ab941348125dbdab773be3a0afaf32361866c7/pdfplumber-0.11.10-py3-none-any.whl", hash = "sha256:7741ea81bf165b474b153e6789d10d18e06b6ddcf3ec84289c3ef2fed6802580", size = 60047, upload-time = "2026-06-15T03:31:29.702Z" }, -] - -[[package]] -name = "pillow" -version = "12.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" }, - { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" }, - { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" }, - { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" }, - { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" }, - { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" }, - { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" }, - { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, - { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, - { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, - { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, - { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, - { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, - { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, - { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, - { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, - { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, - { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, - { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, - { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, - { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, - { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, - { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, - { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, - { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, - { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, - { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, - { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, - { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, - { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, - { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, - { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, - { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, - { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, - { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, - { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, - { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, - { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, - { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" }, - { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" }, - { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" }, - { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" }, - { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" }, - { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, -] - [[package]] name = "platformdirs" version = "4.10.0" @@ -3129,358 +1582,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] -[[package]] -name = "portalocker" -version = "2.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pywin32", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1f/f8/969e6f280201b40b31bcb62843c619f343dcc351dff83a5891530c9dd60e/portalocker-2.7.0.tar.gz", hash = "sha256:032e81d534a88ec1736d03f780ba073f047a06c478b06e2937486f334e955c51", size = 20183, upload-time = "2023-01-18T23:36:14.436Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/df/d4f711d168524f5aebd7fb30969eaa31e3048cf8979688cde3b08f6e5eb8/portalocker-2.7.0-py2.py3-none-any.whl", hash = "sha256:a07c5b4f3985c3cf4798369631fb7011adb498e2a46d8440efc75a8f29a0f983", size = 15502, upload-time = "2023-01-18T23:36:12.849Z" }, -] - -[[package]] -name = "posthog" -version = "5.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "backoff" }, - { name = "distro" }, - { name = "python-dateutil" }, - { name = "requests" }, - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/48/20/60ae67bb9d82f00427946218d49e2e7e80fb41c15dc5019482289ec9ce8d/posthog-5.4.0.tar.gz", hash = "sha256:701669261b8d07cdde0276e5bc096b87f9e200e3b9589c5ebff14df658c5893c", size = 88076, upload-time = "2025-06-20T23:19:23.485Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/98/e480cab9a08d1c09b1c59a93dade92c1bb7544826684ff2acbfd10fcfbd4/posthog-5.4.0-py3-none-any.whl", hash = "sha256:284dfa302f64353484420b52d4ad81ff5c2c2d1d607c4e2db602ac72761831bd", size = 105364, upload-time = "2025-06-20T23:19:22.001Z" }, -] - -[[package]] -name = "propcache" -version = "0.5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/f1/8a8cc1c2c7e7934ab77e0163414f736fadbc0f5e8dd9673b952355ac175b/propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78", size = 90744, upload-time = "2026-05-08T20:59:45.799Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f4/651b1225e976bd1a2ba5cfba0c29d096581c2636b437e3a9a7ab6276270a/propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959", size = 52033, upload-time = "2026-05-08T20:59:47.408Z" }, - { url = "https://files.pythonhosted.org/packages/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7", size = 52754, upload-time = "2026-05-08T20:59:49.202Z" }, - { url = "https://files.pythonhosted.org/packages/7d/fe/b3551b41bbc2f5b5bb088fc6920567cd43101253e68fbaa261339eb96fe1/propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511", size = 57573, upload-time = "2026-05-08T20:59:50.778Z" }, - { url = "https://files.pythonhosted.org/packages/83/27/ab851ebd1b7172e3e161f5f8d39e315d54a91bea246f01f4d872d3376aef/propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660", size = 60645, upload-time = "2026-05-08T20:59:52.227Z" }, - { url = "https://files.pythonhosted.org/packages/95/7d/466b3d18022e9897cbda9c735c493c5bd747d7a4c6f5ea1480b4cec434b6/propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66", size = 61563, upload-time = "2026-05-08T20:59:53.866Z" }, - { url = "https://files.pythonhosted.org/packages/27/1b/16ab7f2cf2041da2f60d156ba64c2484eadf9168075b4ff43c3ef60045af/propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b", size = 58888, upload-time = "2026-05-08T20:59:55.457Z" }, - { url = "https://files.pythonhosted.org/packages/0a/67/bb777ffd907633563bf35fd859c4ce97b0512c32f4633cf5d1eb7c33512b/propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67", size = 59253, upload-time = "2026-05-08T20:59:57.075Z" }, - { url = "https://files.pythonhosted.org/packages/b9/42/64f8d90b73fd9cdc1499b48057ff6d9cd2a98a25734c9bb62ecf07e87061/propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f", size = 57558, upload-time = "2026-05-08T20:59:58.602Z" }, - { url = "https://files.pythonhosted.org/packages/eb/02/dba5bc03c9041f2092ea55a449caf5dfe68352c6654511b29ba0654ddb69/propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c", size = 55007, upload-time = "2026-05-08T20:59:59.837Z" }, - { url = "https://files.pythonhosted.org/packages/14/c0/43f649c7aa2a77a3b100d84e9dea3a483120ecb608bfe36ce49eaff517fe/propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0", size = 60355, upload-time = "2026-05-08T21:00:01.144Z" }, - { url = "https://files.pythonhosted.org/packages/83/c0/435dafd27f1cb4a495381dae60e25883ccfe4020bb72818e8184c1678092/propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6", size = 59057, upload-time = "2026-05-08T21:00:02.401Z" }, - { url = "https://files.pythonhosted.org/packages/53/ae/6e292df9135d659944e96cb3389258e4a663e5b2b5f6c217ef0ddc8d2f73/propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27", size = 61938, upload-time = "2026-05-08T21:00:03.638Z" }, - { url = "https://files.pythonhosted.org/packages/0b/42/314ebc50d8159055411fd6b0bda322ff510e4b1f7d2e4927940ad0f6af20/propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f", size = 59731, upload-time = "2026-05-08T21:00:04.881Z" }, - { url = "https://files.pythonhosted.org/packages/b8/9b/2da6dee38871c3c8772fabc2758325a5c9077d6d18c597737dc04dd884cd/propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0", size = 38966, upload-time = "2026-05-08T21:00:06.511Z" }, - { url = "https://files.pythonhosted.org/packages/42/4e/f17363fb58c0afe05b067361cb6d86ed2d29de6506779a27547c4d183075/propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82", size = 42135, upload-time = "2026-05-08T21:00:08.088Z" }, - { url = "https://files.pythonhosted.org/packages/c6/eb/6af6685077d22e8b33358d3c548e3282706a0b3cd85044ffba4e5dd08e3b/propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab", size = 38381, upload-time = "2026-05-08T21:00:09.692Z" }, - { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, - { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, - { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, - { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, - { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, - { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, - { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, - { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, - { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, - { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, - { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, - { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, - { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, - { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" }, - { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" }, - { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" }, - { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" }, - { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" }, - { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" }, - { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" }, - { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" }, - { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" }, - { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" }, - { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" }, - { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" }, - { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" }, - { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" }, - { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" }, - { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" }, - { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" }, - { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" }, - { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" }, - { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" }, - { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" }, - { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" }, - { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" }, - { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" }, - { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" }, - { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" }, - { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" }, - { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" }, - { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" }, - { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" }, - { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" }, - { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" }, - { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" }, - { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" }, - { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" }, - { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" }, - { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" }, - { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" }, - { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" }, - { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" }, - { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" }, - { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" }, - { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" }, - { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" }, - { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" }, - { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" }, - { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" }, - { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" }, - { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" }, - { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" }, - { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" }, - { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" }, - { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" }, - { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" }, - { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" }, - { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" }, - { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, -] - -[[package]] -name = "protobuf" -version = "7.35.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, - { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, - { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, - { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, - { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, - { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, - { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, -] - -[[package]] -name = "pyarrow" -version = "24.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/13/13e1069b351bdc3881266e11147ffccf687505dbb0ea74036237f5d454a5/pyarrow-24.0.0.tar.gz", hash = "sha256:85fe721a14dd823aca09127acbb06c3ca723efbd436c004f16bca601b04dcc83", size = 1180261, upload-time = "2026-04-21T10:51:25.837Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/c9/a47ab7ece0d86cbe6678418a0fbd1ac4bb493b9184a3891dfa0e7f287ae0/pyarrow-24.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b0e131f880cda8d04e076cee175a46fc0e8bc8b65c99c6c09dff6669335fde74", size = 35068898, upload-time = "2026-04-21T10:46:36.599Z" }, - { url = "https://files.pythonhosted.org/packages/d1/bc/8db86617a9a58008acf8913d6fed68ea2a46acb6de928db28d724c891a68/pyarrow-24.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:1b2fe7f9a5566401a0ef2571f197eb92358925c1f0c8dba305d6e43ea0871bb3", size = 36679915, upload-time = "2026-04-21T10:46:42.602Z" }, - { url = "https://files.pythonhosted.org/packages/eb/8e/fb178720400ef69db251eb4a9c3ccf4af269bc1feb5055529b8fc87170d1/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0b3537c00fb8d384f15ac1e79b6eb6db04a16514c8c1d22e59a9b95c8ba42868", size = 45697931, upload-time = "2026-04-21T10:46:48.403Z" }, - { url = "https://files.pythonhosted.org/packages/f3/27/99c42abe8e21b44f4917f62631f3aa31404882a2c41d8a4cd5c110e13d52/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:14e31a3c9e35f1ab6356c6378f6f72830e6d2d5f1791df3774a7b097d18a6a1e", size = 48837449, upload-time = "2026-04-21T10:46:55.329Z" }, - { url = "https://files.pythonhosted.org/packages/36/b6/333749e2666e9032891125bf9c691146e92901bece62030ac1430e2e7c88/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7d9a514e73bc42711e6a35aaccf3587c520024fe0a25d830a1a8a27c15f4f57", size = 49395949, upload-time = "2026-04-21T10:47:01.869Z" }, - { url = "https://files.pythonhosted.org/packages/17/25/c5201706a2dd374e8ba6ee3fd7a8c89fb7ffc16eed5217a91fd2bd7f7626/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b196eb3f931862af3fa84c2a253514d859c08e0d8fe020e07be12e75a5a9780c", size = 51912986, upload-time = "2026-04-21T10:47:09.872Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d2/4d1bbba65320b21a49678d6fbdc6ff7c649251359fdcfc03568c4136231d/pyarrow-24.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:35405aecb474e683fb36af650618fd5340ee5471fc65a21b36076a18bbc6c981", size = 27255371, upload-time = "2026-04-21T10:47:15.943Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a9/9686d9f07837f91f775e8932659192e02c74f9d8920524b480b85212cc68/pyarrow-24.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6233c9ed9ab9d1db47de57d9753256d9dcffbf42db341576099f0fd9f6bf4810", size = 34981559, upload-time = "2026-04-21T10:47:22.17Z" }, - { url = "https://files.pythonhosted.org/packages/80/b6/0ddf0e9b6ead3474ab087ae598c76b031fc45532bf6a63f3a553440fb258/pyarrow-24.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:f7616236ec1bc2b15bfdec22a71ab38851c86f8f05ff64f379e1278cf20c634a", size = 36663654, upload-time = "2026-04-21T10:47:28.315Z" }, - { url = "https://files.pythonhosted.org/packages/7c/3b/926382efe8ce27ba729071d3566ade6dfb86bdf112f366000196b2f5780a/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1617043b99bd33e5318ae18eb2919af09c71322ef1ca46566cdafc6e6712fb66", size = 45679394, upload-time = "2026-04-21T10:47:34.821Z" }, - { url = "https://files.pythonhosted.org/packages/b3/7a/829f7d9dfd37c207206081d6dad474d81dde29952401f07f2ba507814818/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6165461f55ef6314f026de6638d661188e3455d3ec49834556a0ebbdbace18bb", size = 48863122, upload-time = "2026-04-21T10:47:42.056Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e8/f88ce625fe8babaae64e8db2d417c7653adb3019b08aae85c5ed787dc816/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b13dedfe76a0ad2d1d859b0811b53827a4e9d93a0bcb05cf59333ab4980cc7e", size = 49376032, upload-time = "2026-04-21T10:47:48.967Z" }, - { url = "https://files.pythonhosted.org/packages/36/7a/82c363caa145fff88fb475da50d3bf52bb024f61917be5424c3392eaf878/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25ea65d868eb04015cd18e6df2fbe98f07e5bda2abefabcb88fce39a947716f6", size = 51929490, upload-time = "2026-04-21T10:47:55.981Z" }, - { url = "https://files.pythonhosted.org/packages/66/1c/e3e72c8014ad2743ca64a701652c733cc5cbcee15c0463a32a8c55518d9e/pyarrow-24.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:295f0a7f2e242dabd513737cf076007dc5b2d59237e3eca37b05c0c6446f3826", size = 27355660, upload-time = "2026-04-21T10:48:01.718Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d3/a1abf004482026ddc17f4503db227787fa3cfe41ec5091ff20e4fea55e57/pyarrow-24.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:02b001b3ed4723caa44f6cd1af2d5c86aa2cf9971dacc2ffa55b21237713dfba", size = 34976759, upload-time = "2026-04-21T10:48:07.258Z" }, - { url = "https://files.pythonhosted.org/packages/4f/4a/34f0a36d28a2dd32225301b79daad44e243dc1a2bb77d43b60749be255c4/pyarrow-24.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:04920d6a71aabd08a0417709efce97d45ea8e6fb733d9ca9ecffb13c67839f68", size = 36658471, upload-time = "2026-04-21T10:48:13.347Z" }, - { url = "https://files.pythonhosted.org/packages/1f/78/543b94712ae8bb1a6023bcc1acf1a740fbff8286747c289cd9468fced2a5/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a964266397740257f16f7bb2e4f08a0c81454004beab8ff59dd531b73610e9f2", size = 45675981, upload-time = "2026-04-21T10:48:20.201Z" }, - { url = "https://files.pythonhosted.org/packages/84/9f/8fb7c222b100d314137fa40ec050de56cd8c6d957d1cfff685ce72f15b17/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f066b179d68c413374294bc1735f68475457c933258df594443bb9d88ddc2a0", size = 48859172, upload-time = "2026-04-21T10:48:27.541Z" }, - { url = "https://files.pythonhosted.org/packages/a7/d3/1ea72538e6c8b3b475ed78d1049a2c518e655761ea50fe1171fc855fcab7/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1183baeb14c5f587b1ec52831e665718ce632caab84b7cd6b85fd44f96114495", size = 49385733, upload-time = "2026-04-21T10:48:34.7Z" }, - { url = "https://files.pythonhosted.org/packages/c3/be/c3d8b06a1ba35f2260f8e1f771abbee7d5e345c0937aab90675706b1690a/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:806f24b4085453c197a5078218d1ee08783ebbba271badd153d1ae22a3ee804f", size = 51934335, upload-time = "2026-04-21T10:48:42.099Z" }, - { url = "https://files.pythonhosted.org/packages/9c/62/89e07a1e7329d2cde3e3c6994ba0839a24977a2beda8be6005ea3d860b99/pyarrow-24.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4505fc6583f7b05ab854934896bcac8253b04ac1171a77dfb73efef92076d91", size = 27271748, upload-time = "2026-04-21T10:49:42.532Z" }, - { url = "https://files.pythonhosted.org/packages/17/1a/cff3a59f80b5b1658549d46611b67163f65e0664431c076ad728bf9d5af4/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1a4e45017efbf115032e4475ee876d525e0e36c742214fbe405332480ecd6275", size = 35238554, upload-time = "2026-04-21T10:48:48.526Z" }, - { url = "https://files.pythonhosted.org/packages/a8/99/cce0f42a327bfef2c420fb6078a3eb834826e5d6697bf3009fe11d2ad051/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:7986f1fa71cee060ad00758bcc79d3a93bab8559bf978fab9e53472a2e25a17b", size = 36782301, upload-time = "2026-04-21T10:48:55.181Z" }, - { url = "https://files.pythonhosted.org/packages/2a/66/8e560d5ff6793ca29aca213c53eec0dd482dd46cb93b2819e5aab52e4252/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d3e0b61e8efb24ed38898e5cdc5fffa9124be480008d401a1f8071500494ae42", size = 45721929, upload-time = "2026-04-21T10:49:03.676Z" }, - { url = "https://files.pythonhosted.org/packages/27/0c/a26e25505d030716e078d9f16eb74973cbf0b33b672884e9f9da1c83b871/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:55a3bc1e3df3b5567b7d27ef551b2283f0c68a5e86f1cd56abc569da4f31335b", size = 48825365, upload-time = "2026-04-21T10:49:11.714Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/771f9ecb0c65e73fe9dccdd1717901b9594f08c4515d000c7c62df573811/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:641f795b361874ac9da5294f8f443dfdbee355cf2bd9e3b8d97aaac2306b9b37", size = 49451819, upload-time = "2026-04-21T10:49:21.474Z" }, - { url = "https://files.pythonhosted.org/packages/48/da/61ae89a88732f5a785646f3ec6125dbb640fa98a540eb2b9889caa561403/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8adc8e6ce5fccf5dc707046ae4914fd537def529709cc0d285d37a7f9cd442ca", size = 51909252, upload-time = "2026-04-21T10:49:31.164Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1a/8dd5cafab7b66573fa91c03d06d213356ad4edd71813aa75e08ce2b3a844/pyarrow-24.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9b18371ad2f44044b81a8d23bc2d8a9b6a6226dca775e8e16cfee640473d6c5d", size = 27388127, upload-time = "2026-04-21T10:49:37.334Z" }, - { url = "https://files.pythonhosted.org/packages/ad/80/d022a34ff05d2cbedd8ccf841fc1f532ecfa9eb5ed1711b56d0e0ea71fc9/pyarrow-24.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:1cc9057f0319e26333b357e17f3c2c022f1a83739b48a88b25bfd5fa2dc18838", size = 35007997, upload-time = "2026-04-21T10:49:48.796Z" }, - { url = "https://files.pythonhosted.org/packages/1a/ff/f01485fda6f4e5d441afb8dd5e7681e4db18826c1e271852f5d3957d6a80/pyarrow-24.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e6f1278ee4785b6db21229374a1c9e54ec7c549de5d1efc9630b6207de7e170b", size = 36678720, upload-time = "2026-04-21T10:49:55.858Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c2/2d2d5fea814237923f71b36495211f20b43a1576f9a4d6da7e751a64ec6f/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:adbbedc55506cbdabb830890444fb856bfb0060c46c6f8026c6c2f2cf86ae795", size = 45741852, upload-time = "2026-04-21T10:50:04.624Z" }, - { url = "https://files.pythonhosted.org/packages/8e/3a/28ba9c1c1ebdbb5f1b94dfebb46f207e52e6a554b7fe4132540fde29a3a0/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ae8a1145af31d903fa9bb166824d7abe9b4681a000b0159c9fb99c11bc11ad26", size = 48889852, upload-time = "2026-04-21T10:50:12.293Z" }, - { url = "https://files.pythonhosted.org/packages/df/51/4a389acfd31dca009f8fb82d7f510bb4130f2b3a8e18cf00194d0687d8ac/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d7027eba1df3b2069e2e8d80f644fa0918b68c46432af3d088ddd390d063ecde", size = 49445207, upload-time = "2026-04-21T10:50:20.677Z" }, - { url = "https://files.pythonhosted.org/packages/19/4b/0bab2b23d2ae901b1b9a03c0efd4b2d070256f8ce3fc43f6e58c167b2081/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e56a1ffe9bf7b727432b89104cc0849c21582949dd7bdcb34f17b2001a351a76", size = 51954117, upload-time = "2026-04-21T10:50:29.14Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/f4e9145da0417b3d2c12035a8492b35ff4a3dbc653e614fcfb51d9dedb38/pyarrow-24.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:38be1808cdd068605b787e6ca9119b27eb275a0234e50212c3492331680c3b1e", size = 28001155, upload-time = "2026-04-21T10:51:22.337Z" }, - { url = "https://files.pythonhosted.org/packages/79/4f/46a49a63f43526da895b1a45bbb51d5baf8e4d77159f8528fc3e5490007f/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:418e48ce50a45a6a6c73c454677203a9c75c966cb1e92ca3370959185f197a05", size = 35250387, upload-time = "2026-04-21T10:50:35.552Z" }, - { url = "https://files.pythonhosted.org/packages/a0/da/d5e0cd5ef00796922404806d5f00325cdadc3441ce2c13fe7115f2df9a64/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2f16197705a230a78270cdd4ea8a1d57e86b2fdcbc34a1f6aebc72e65c986f9a", size = 36797102, upload-time = "2026-04-21T10:50:42.417Z" }, - { url = "https://files.pythonhosted.org/packages/34/c7/5904145b0a593a05236c882933d439b5720f0a145381179063722fbfc123/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fb24ac194bfc5e86839d7dcd52092ee31e5fe6733fe11f5e3b06ef0812b20072", size = 45745118, upload-time = "2026-04-21T10:50:49.324Z" }, - { url = "https://files.pythonhosted.org/packages/13/d3/cca42fe166d1c6e4d5b80e530b7949104d10e17508a90ae202dac205ce2a/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9700ebd9a51f5895ce75ff4ac4b3c47a7d4b42bc618be8e713e5d56bacf5f931", size = 48844765, upload-time = "2026-04-21T10:50:55.579Z" }, - { url = "https://files.pythonhosted.org/packages/b0/49/942c3b79878ba928324d1e17c274ed84581db8c0a749b24bcf4cbdf15bd3/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d8ddd2768da81d3ee08cfea9b597f4abb4e8e1dc8ae7e204b608d23a0d3ab699", size = 49471890, upload-time = "2026-04-21T10:51:02.439Z" }, - { url = "https://files.pythonhosted.org/packages/76/97/ff71431000a75d84135a1ace5ca4ba11726a231a8007bbb320a4c54075d5/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:61a3d7eaa97a14768b542f3d284dc6400dd2470d9f080708b13cd46b6ae18136", size = 51932250, upload-time = "2026-04-21T10:51:10.576Z" }, - { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, -] - -[[package]] -name = "pybase64" -version = "1.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/b8/4ed5c7ad5ec15b08d35cc79ace6145d5c1ae426e46435f4987379439dfea/pybase64-1.4.3.tar.gz", hash = "sha256:c2ed274c9e0ba9c8f9c4083cfe265e66dd679126cd9c2027965d807352f3f053", size = 137272, upload-time = "2025-12-06T13:27:04.013Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/63/21e981e9d3f1f123e0b0ee2130112b1956cad9752309f574862c7ae77c08/pybase64-1.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:70b0d4a4d54e216ce42c2655315378b8903933ecfa32fced453989a92b4317b2", size = 38237, upload-time = "2025-12-06T13:22:52.159Z" }, - { url = "https://files.pythonhosted.org/packages/92/fb/3f448e139516404d2a3963915cc10dc9dde7d3a67de4edba2f827adfef17/pybase64-1.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8127f110cdee7a70e576c5c9c1d4e17e92e76c191869085efbc50419f4ae3c72", size = 31673, upload-time = "2025-12-06T13:22:53.241Z" }, - { url = "https://files.pythonhosted.org/packages/3c/fb/bb06a5b9885e7d853ac1e801c4d8abfdb4c8506deee33e53d55aa6690e67/pybase64-1.4.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f9ef0388878bc15a084bd9bf73ec1b2b4ee513d11009b1506375e10a7aae5032", size = 68331, upload-time = "2025-12-06T13:22:54.197Z" }, - { url = "https://files.pythonhosted.org/packages/64/15/8d60b9ec5e658185fc2ee3333e01a6e30d717cf677b24f47cbb3a859d13c/pybase64-1.4.3-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95a57cccf106352a72ed8bc8198f6820b16cc7d55aa3867a16dea7011ae7c218", size = 71370, upload-time = "2025-12-06T13:22:55.517Z" }, - { url = "https://files.pythonhosted.org/packages/ac/29/a3e5c1667cc8c38d025a4636855de0fc117fc62e2afeb033a3c6f12c6a22/pybase64-1.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cd1c47dfceb9c7bd3de210fb4e65904053ed2d7c9dce6d107f041ff6fbd7e21", size = 59834, upload-time = "2025-12-06T13:22:56.682Z" }, - { url = "https://files.pythonhosted.org/packages/a9/00/8ffcf9810bd23f3984698be161cf7edba656fd639b818039a7be1d6405d4/pybase64-1.4.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:9fe9922698f3e2f72874b26890d53a051c431d942701bb3a37aae94da0b12107", size = 56652, upload-time = "2025-12-06T13:22:57.724Z" }, - { url = "https://files.pythonhosted.org/packages/81/62/379e347797cdea4ab686375945bc77ad8d039c688c0d4d0cfb09d247beb9/pybase64-1.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:af5f4bd29c86b59bb4375e0491d16ec8a67548fa99c54763aaedaf0b4b5a6632", size = 59382, upload-time = "2025-12-06T13:22:58.758Z" }, - { url = "https://files.pythonhosted.org/packages/c6/f2/9338ffe2f487086f26a2c8ca175acb3baa86fce0a756ff5670a0822bb877/pybase64-1.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c302f6ca7465262908131411226e02100f488f531bb5e64cb901aa3f439bccd9", size = 59990, upload-time = "2025-12-06T13:23:01.007Z" }, - { url = "https://files.pythonhosted.org/packages/f9/a4/85a6142b65b4df8625b337727aa81dc199642de3d09677804141df6ee312/pybase64-1.4.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:2f3f439fa4d7fde164ebbbb41968db7d66b064450ab6017c6c95cef0afa2b349", size = 54923, upload-time = "2025-12-06T13:23:02.369Z" }, - { url = "https://files.pythonhosted.org/packages/ac/00/e40215d25624012bf5b7416ca37f168cb75f6dd15acdb91ea1f2ea4dc4e7/pybase64-1.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a23c6866551043f8b681a5e1e0d59469148b2920a3b4fc42b1275f25ea4217a", size = 58664, upload-time = "2025-12-06T13:23:03.378Z" }, - { url = "https://files.pythonhosted.org/packages/b0/73/d7e19a63e795c13837f2356268d95dc79d1180e756f57ced742a1e52fdeb/pybase64-1.4.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:56e6526f8565642abc5f84338cc131ce298a8ccab696b19bdf76fa6d7dc592ef", size = 52338, upload-time = "2025-12-06T13:23:04.458Z" }, - { url = "https://files.pythonhosted.org/packages/f2/32/3c746d7a310b69bdd9df77ffc85c41b80bce00a774717596f869b0d4a20e/pybase64-1.4.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6a792a8b9d866ffa413c9687d9b611553203753987a3a582d68cbc51cf23da45", size = 68993, upload-time = "2025-12-06T13:23:05.526Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b3/63cec68f9d6f6e4c0b438d14e5f1ef536a5fe63ce14b70733ac5e31d7ab8/pybase64-1.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:62ad29a5026bb22cfcd1ca484ec34b0a5ced56ddba38ceecd9359b2818c9c4f9", size = 58055, upload-time = "2025-12-06T13:23:06.931Z" }, - { url = "https://files.pythonhosted.org/packages/d5/cb/7acf7c3c06f9692093c07f109668725dc37fb9a3df0fa912b50add645195/pybase64-1.4.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11b9d1d2d32ec358c02214363b8fc3651f6be7dd84d880ecd597a6206a80e121", size = 54430, upload-time = "2025-12-06T13:23:07.936Z" }, - { url = "https://files.pythonhosted.org/packages/33/39/4eb33ff35d173bfff4002e184ce8907f5d0a42d958d61cd9058ef3570179/pybase64-1.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0aebaa7f238caa0a0d373616016e2040c6c879ebce3ba7ab3c59029920f13640", size = 56272, upload-time = "2025-12-06T13:23:09.253Z" }, - { url = "https://files.pythonhosted.org/packages/19/97/a76d65c375a254e65b730c6f56bf528feca91305da32eceab8bcc08591e6/pybase64-1.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e504682b20c63c2b0c000e5f98a80ea867f8d97642e042a5a39818e44ba4d599", size = 70904, upload-time = "2025-12-06T13:23:10.336Z" }, - { url = "https://files.pythonhosted.org/packages/5e/2c/8338b6d3da3c265002839e92af0a80d6db88385c313c73f103dfb800c857/pybase64-1.4.3-cp311-cp311-win32.whl", hash = "sha256:e9a8b81984e3c6fb1db9e1614341b0a2d98c0033d693d90c726677db1ffa3a4c", size = 33639, upload-time = "2025-12-06T13:23:11.9Z" }, - { url = "https://files.pythonhosted.org/packages/39/dc/32efdf2f5927e5449cc341c266a1bbc5fecd5319a8807d9c5405f76e6d02/pybase64-1.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:a90a8fa16a901fabf20de824d7acce07586e6127dc2333f1de05f73b1f848319", size = 35797, upload-time = "2025-12-06T13:23:13.174Z" }, - { url = "https://files.pythonhosted.org/packages/da/59/eda4f9cb0cbce5a45f0cd06131e710674f8123a4d570772c5b9694f88559/pybase64-1.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:61d87de5bc94d143622e94390ec3e11b9c1d4644fe9be3a81068ab0f91056f59", size = 31160, upload-time = "2025-12-06T13:23:15.696Z" }, - { url = "https://files.pythonhosted.org/packages/86/a7/efcaa564f091a2af7f18a83c1c4875b1437db56ba39540451dc85d56f653/pybase64-1.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18d85e5ab8b986bb32d8446aca6258ed80d1bafe3603c437690b352c648f5967", size = 38167, upload-time = "2025-12-06T13:23:16.821Z" }, - { url = "https://files.pythonhosted.org/packages/db/c7/c7ad35adff2d272bf2930132db2b3eea8c44bb1b1f64eb9b2b8e57cde7b4/pybase64-1.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f5791a3491d116d0deaf4d83268f48792998519698f8751efb191eac84320e9", size = 31673, upload-time = "2025-12-06T13:23:17.835Z" }, - { url = "https://files.pythonhosted.org/packages/43/1b/9a8cab0042b464e9a876d5c65fe5127445a2436da36fda64899b119b1a1b/pybase64-1.4.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f0b3f200c3e06316f6bebabd458b4e4bcd4c2ca26af7c0c766614d91968dee27", size = 68210, upload-time = "2025-12-06T13:23:18.813Z" }, - { url = "https://files.pythonhosted.org/packages/62/f7/965b79ff391ad208b50e412b5d3205ccce372a2d27b7218ae86d5295b105/pybase64-1.4.3-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb632edfd132b3eaf90c39c89aa314beec4e946e210099b57d40311f704e11d4", size = 71599, upload-time = "2025-12-06T13:23:20.195Z" }, - { url = "https://files.pythonhosted.org/packages/03/4b/a3b5175130b3810bbb8ccfa1edaadbd3afddb9992d877c8a1e2f274b476e/pybase64-1.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:356ef1d74648ce997f5a777cf8f1aefecc1c0b4fe6201e0ef3ec8a08170e1b54", size = 59922, upload-time = "2025-12-06T13:23:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/da/5d/c38d1572027fc601b62d7a407721688b04b4d065d60ca489912d6893e6cf/pybase64-1.4.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:c48361f90db32bacaa5518419d4eb9066ba558013aaf0c7781620279ecddaeb9", size = 56712, upload-time = "2025-12-06T13:23:22.77Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d4/4e04472fef485caa8f561d904d4d69210a8f8fc1608ea15ebd9012b92655/pybase64-1.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:702bcaa16ae02139d881aeaef5b1c8ffb4a3fae062fe601d1e3835e10310a517", size = 59300, upload-time = "2025-12-06T13:23:24.543Z" }, - { url = "https://files.pythonhosted.org/packages/86/e7/16e29721b86734b881d09b7e23dfd7c8408ad01a4f4c7525f3b1088e25ec/pybase64-1.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:53d0ffe1847b16b647c6413d34d1de08942b7724273dd57e67dcbdb10c574045", size = 60278, upload-time = "2025-12-06T13:23:25.608Z" }, - { url = "https://files.pythonhosted.org/packages/b1/02/18515f211d7c046be32070709a8efeeef8a0203de4fd7521e6b56404731b/pybase64-1.4.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9a1792e8b830a92736dae58f0c386062eb038dfe8004fb03ba33b6083d89cd43", size = 54817, upload-time = "2025-12-06T13:23:26.633Z" }, - { url = "https://files.pythonhosted.org/packages/e7/be/14e29d8e1a481dbff151324c96dd7b5d2688194bb65dc8a00ca0e1ad1e86/pybase64-1.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d468b1b1ac5ad84875a46eaa458663c3721e8be5f155ade356406848d3701f6", size = 58611, upload-time = "2025-12-06T13:23:27.684Z" }, - { url = "https://files.pythonhosted.org/packages/b4/8a/a2588dfe24e1bbd742a554553778ab0d65fdf3d1c9a06d10b77047d142aa/pybase64-1.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e97b7bdbd62e71898cd542a6a9e320d9da754ff3ebd02cb802d69087ee94d468", size = 52404, upload-time = "2025-12-06T13:23:28.714Z" }, - { url = "https://files.pythonhosted.org/packages/27/fc/afcda7445bebe0cbc38cafdd7813234cdd4fc5573ff067f1abf317bb0cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b33aeaa780caaa08ffda87fc584d5eab61e3d3bbb5d86ead02161dc0c20d04bc", size = 68817, upload-time = "2025-12-06T13:23:30.079Z" }, - { url = "https://files.pythonhosted.org/packages/d3/3a/87c3201e555ed71f73e961a787241a2438c2bbb2ca8809c29ddf938a3157/pybase64-1.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c0efcf78f11cf866bed49caa7b97552bc4855a892f9cc2372abcd3ed0056f0d", size = 57854, upload-time = "2025-12-06T13:23:31.17Z" }, - { url = "https://files.pythonhosted.org/packages/fd/7d/931c2539b31a7b375e7d595b88401eeb5bd6c5ce1059c9123f9b608aaa14/pybase64-1.4.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:66e3791f2ed725a46593f8bd2761ff37d01e2cdad065b1dceb89066f476e50c6", size = 54333, upload-time = "2025-12-06T13:23:32.422Z" }, - { url = "https://files.pythonhosted.org/packages/de/5e/537601e02cc01f27e9d75f440f1a6095b8df44fc28b1eef2cd739aea8cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:72bb0b6bddadab26e1b069bb78e83092711a111a80a0d6b9edcb08199ad7299b", size = 56492, upload-time = "2025-12-06T13:23:33.515Z" }, - { url = "https://files.pythonhosted.org/packages/96/97/2a2e57acf8f5c9258d22aba52e71f8050e167b29ed2ee1113677c1b600c1/pybase64-1.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5b3365dbcbcdb0a294f0f50af0c0a16b27a232eddeeb0bceeefd844ef30d2a23", size = 70974, upload-time = "2025-12-06T13:23:36.27Z" }, - { url = "https://files.pythonhosted.org/packages/75/2e/a9e28941c6dab6f06e6d3f6783d3373044be9b0f9a9d3492c3d8d2260ac0/pybase64-1.4.3-cp312-cp312-win32.whl", hash = "sha256:7bca1ed3a5df53305c629ca94276966272eda33c0d71f862d2d3d043f1e1b91a", size = 33686, upload-time = "2025-12-06T13:23:37.848Z" }, - { url = "https://files.pythonhosted.org/packages/83/e3/507ab649d8c3512c258819c51d25c45d6e29d9ca33992593059e7b646a33/pybase64-1.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:9f2da8f56d9b891b18b4daf463a0640eae45a80af548ce435be86aa6eff3603b", size = 35833, upload-time = "2025-12-06T13:23:38.877Z" }, - { url = "https://files.pythonhosted.org/packages/bc/8a/6eba66cd549a2fc74bb4425fd61b839ba0ab3022d3c401b8a8dc2cc00c7a/pybase64-1.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:0631d8a2d035de03aa9bded029b9513e1fee8ed80b7ddef6b8e9389ffc445da0", size = 31185, upload-time = "2025-12-06T13:23:39.908Z" }, - { url = "https://files.pythonhosted.org/packages/3a/50/b7170cb2c631944388fe2519507fe3835a4054a6a12a43f43781dae82be1/pybase64-1.4.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:ea4b785b0607d11950b66ce7c328f452614aefc9c6d3c9c28bae795dc7f072e1", size = 33901, upload-time = "2025-12-06T13:23:40.951Z" }, - { url = "https://files.pythonhosted.org/packages/48/8b/69f50578e49c25e0a26e3ee72c39884ff56363344b79fc3967f5af420ed6/pybase64-1.4.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:6a10b6330188c3026a8b9c10e6b9b3f2e445779cf16a4c453d51a072241c65a2", size = 40807, upload-time = "2025-12-06T13:23:42.006Z" }, - { url = "https://files.pythonhosted.org/packages/5c/8d/20b68f11adfc4c22230e034b65c71392e3e338b413bf713c8945bd2ccfb3/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:27fdff227a0c0e182e0ba37a99109645188978b920dfb20d8b9c17eeee370d0d", size = 30932, upload-time = "2025-12-06T13:23:43.348Z" }, - { url = "https://files.pythonhosted.org/packages/f7/79/b1b550ac6bff51a4880bf6e089008b2e1ca16f2c98db5e039a08ac3ad157/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2a8204f1fdfec5aa4184249b51296c0de95445869920c88123978304aad42df1", size = 31394, upload-time = "2025-12-06T13:23:44.317Z" }, - { url = "https://files.pythonhosted.org/packages/82/70/b5d7c5932bf64ee1ec5da859fbac981930b6a55d432a603986c7f509c838/pybase64-1.4.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:874fc2a3777de6baf6aa921a7aa73b3be98295794bea31bd80568a963be30767", size = 38078, upload-time = "2025-12-06T13:23:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/56/fe/e66fe373bce717c6858427670736d54297938dad61c5907517ab4106bd90/pybase64-1.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2dc64a94a9d936b8e3449c66afabbaa521d3cc1a563d6bbaaa6ffa4535222e4b", size = 38158, upload-time = "2025-12-06T13:23:46.872Z" }, - { url = "https://files.pythonhosted.org/packages/80/a9/b806ed1dcc7aed2ea3dd4952286319e6f3a8b48615c8118f453948e01999/pybase64-1.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e48f86de1c145116ccf369a6e11720ce696c2ec02d285f440dfb57ceaa0a6cb4", size = 31672, upload-time = "2025-12-06T13:23:47.88Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c9/24b3b905cf75e23a9a4deaf203b35ffcb9f473ac0e6d8257f91a05dfce62/pybase64-1.4.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:1d45c8fe8fe82b65c36b227bb4a2cf623d9ada16bed602ce2d3e18c35285b72a", size = 68244, upload-time = "2025-12-06T13:23:49.026Z" }, - { url = "https://files.pythonhosted.org/packages/f8/cd/d15b0c3e25e5859fab0416dc5b96d34d6bd2603c1c96a07bb2202b68ab92/pybase64-1.4.3-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad70c26ba091d8f5167e9d4e1e86a0483a5414805cdb598a813db635bd3be8b8", size = 71620, upload-time = "2025-12-06T13:23:50.081Z" }, - { url = "https://files.pythonhosted.org/packages/0d/31/4ca953cc3dcde2b3711d6bfd70a6f4ad2ca95a483c9698076ba605f1520f/pybase64-1.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e98310b7c43145221e7194ac9fa7fffc84763c87bfc5e2f59f9f92363475bdc1", size = 59930, upload-time = "2025-12-06T13:23:51.68Z" }, - { url = "https://files.pythonhosted.org/packages/60/55/e7f7bdcd0fd66e61dda08db158ffda5c89a306bbdaaf5a062fbe4e48f4a1/pybase64-1.4.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:398685a76034e91485a28aeebcb49e64cd663212fd697b2497ac6dfc1df5e671", size = 56425, upload-time = "2025-12-06T13:23:52.732Z" }, - { url = "https://files.pythonhosted.org/packages/cb/65/b592c7f921e51ca1aca3af5b0d201a98666d0a36b930ebb67e7c2ed27395/pybase64-1.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7e46400a6461187ccb52ed75b0045d937529e801a53a9cd770b350509f9e4d50", size = 59327, upload-time = "2025-12-06T13:23:53.856Z" }, - { url = "https://files.pythonhosted.org/packages/23/95/1613d2fb82dbb1548595ad4179f04e9a8451bfa18635efce18b631eabe3f/pybase64-1.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1b62b9f2f291d94f5e0b76ab499790b7dcc78a009d4ceea0b0428770267484b6", size = 60294, upload-time = "2025-12-06T13:23:54.937Z" }, - { url = "https://files.pythonhosted.org/packages/9d/73/40431f37f7d1b3eab4673e7946ff1e8f5d6bd425ec257e834dae8a6fc7b0/pybase64-1.4.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:f30ceb5fa4327809dede614be586efcbc55404406d71e1f902a6fdcf322b93b2", size = 54858, upload-time = "2025-12-06T13:23:56.031Z" }, - { url = "https://files.pythonhosted.org/packages/a7/84/f6368bcaf9f743732e002a9858646fd7a54f428490d427dd6847c5cfe89e/pybase64-1.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0d5f18ed53dfa1d4cf8b39ee542fdda8e66d365940e11f1710989b3cf4a2ed66", size = 58629, upload-time = "2025-12-06T13:23:57.12Z" }, - { url = "https://files.pythonhosted.org/packages/43/75/359532f9adb49c6b546cafc65c46ed75e2ccc220d514ba81c686fbd83965/pybase64-1.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:119d31aa4b58b85a8ebd12b63c07681a138c08dfc2fe5383459d42238665d3eb", size = 52448, upload-time = "2025-12-06T13:23:58.298Z" }, - { url = "https://files.pythonhosted.org/packages/92/6c/ade2ba244c3f33ed920a7ed572ad772eb0b5f14480b72d629d0c9e739a40/pybase64-1.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3cf0218b0e2f7988cf7d738a73b6a1d14f3be6ce249d7c0f606e768366df2cce", size = 68841, upload-time = "2025-12-06T13:23:59.886Z" }, - { url = "https://files.pythonhosted.org/packages/a0/51/b345139cd236be382f2d4d4453c21ee6299e14d2f759b668e23080f8663f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:12f4ee5e988bc5c0c1106b0d8fc37fb0508f12dab76bac1b098cb500d148da9d", size = 57910, upload-time = "2025-12-06T13:24:00.994Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b8/9f84bdc4f1c4f0052489396403c04be2f9266a66b70c776001eaf0d78c1f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:937826bc7b6b95b594a45180e81dd4d99bd4dd4814a443170e399163f7ff3fb6", size = 54335, upload-time = "2025-12-06T13:24:02.046Z" }, - { url = "https://files.pythonhosted.org/packages/d0/c7/be63b617d284de46578a366da77ede39c8f8e815ed0d82c7c2acca560fab/pybase64-1.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:88995d1460971ef80b13e3e007afbe4b27c62db0508bc7250a2ab0a0b4b91362", size = 56486, upload-time = "2025-12-06T13:24:03.141Z" }, - { url = "https://files.pythonhosted.org/packages/5e/96/f252c8f9abd6ded3ef1ccd3cdbb8393a33798007f761b23df8de1a2480e6/pybase64-1.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:72326fe163385ed3e1e806dd579d47fde5d8a59e51297a60fc4e6cbc1b4fc4ed", size = 70978, upload-time = "2025-12-06T13:24:04.221Z" }, - { url = "https://files.pythonhosted.org/packages/af/51/0f5714af7aeef96e30f968e4371d75ad60558aaed3579d7c6c8f1c43c18a/pybase64-1.4.3-cp313-cp313-win32.whl", hash = "sha256:b1623730c7892cf5ed0d6355e375416be6ef8d53ab9b284f50890443175c0ac3", size = 33684, upload-time = "2025-12-06T13:24:05.29Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ad/0cea830a654eb08563fb8214150ef57546ece1cc421c09035f0e6b0b5ea9/pybase64-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:8369887590f1646a5182ca2fb29252509da7ae31d4923dbb55d3e09da8cc4749", size = 35832, upload-time = "2025-12-06T13:24:06.35Z" }, - { url = "https://files.pythonhosted.org/packages/b4/0d/eec2a8214989c751bc7b4cad1860eb2c6abf466e76b77508c0f488c96a37/pybase64-1.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:860b86bca71e5f0237e2ab8b2d9c4c56681f3513b1bf3e2117290c1963488390", size = 31175, upload-time = "2025-12-06T13:24:07.419Z" }, - { url = "https://files.pythonhosted.org/packages/db/c9/e23463c1a2913686803ef76b1a5ae7e6fac868249a66e48253d17ad7232c/pybase64-1.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eb51db4a9c93215135dccd1895dca078e8785c357fabd983c9f9a769f08989a9", size = 38497, upload-time = "2025-12-06T13:24:08.873Z" }, - { url = "https://files.pythonhosted.org/packages/71/83/343f446b4b7a7579bf6937d2d013d82f1a63057cf05558e391ab6039d7db/pybase64-1.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a03ef3f529d85fd46b89971dfb00c634d53598d20ad8908fb7482955c710329d", size = 32076, upload-time = "2025-12-06T13:24:09.975Z" }, - { url = "https://files.pythonhosted.org/packages/46/fc/cb64964c3b29b432f54d1bce5e7691d693e33bbf780555151969ffd95178/pybase64-1.4.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2e745f2ce760c6cf04d8a72198ef892015ddb89f6ceba489e383518ecbdb13ab", size = 72317, upload-time = "2025-12-06T13:24:11.129Z" }, - { url = "https://files.pythonhosted.org/packages/0a/b7/fab2240da6f4e1ad46f71fa56ec577613cf5df9dce2d5b4cfaa4edd0e365/pybase64-1.4.3-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fac217cd9de8581a854b0ac734c50fd1fa4b8d912396c1fc2fce7c230efe3a7", size = 75534, upload-time = "2025-12-06T13:24:12.433Z" }, - { url = "https://files.pythonhosted.org/packages/91/3b/3e2f2b6e68e3d83ddb9fa799f3548fb7449765daec9bbd005a9fbe296d7f/pybase64-1.4.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:da1ee8fa04b283873de2d6e8fa5653e827f55b86bdf1a929c5367aaeb8d26f8a", size = 65399, upload-time = "2025-12-06T13:24:13.928Z" }, - { url = "https://files.pythonhosted.org/packages/6b/08/476ac5914c3b32e0274a2524fc74f01cbf4f4af4513d054e41574eb018f6/pybase64-1.4.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:b0bf8e884ee822ca7b1448eeb97fa131628fe0ff42f60cae9962789bd562727f", size = 60487, upload-time = "2025-12-06T13:24:15.177Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b8/618a92915330cc9cba7880299b546a1d9dab1a21fd6c0292ee44a4fe608c/pybase64-1.4.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1bf749300382a6fd1f4f255b183146ef58f8e9cb2f44a077b3a9200dfb473a77", size = 63959, upload-time = "2025-12-06T13:24:16.854Z" }, - { url = "https://files.pythonhosted.org/packages/a5/52/af9d8d051652c3051862c442ec3861259c5cdb3fc69774bc701470bd2a59/pybase64-1.4.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:153a0e42329b92337664cfc356f2065248e6c9a1bd651bbcd6dcaf15145d3f06", size = 64874, upload-time = "2025-12-06T13:24:18.328Z" }, - { url = "https://files.pythonhosted.org/packages/e4/51/5381a7adf1f381bd184d33203692d3c57cf8ae9f250f380c3fecbdbe554b/pybase64-1.4.3-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:86ee56ac7f2184ca10217ed1c655c1a060273e233e692e9086da29d1ae1768db", size = 58572, upload-time = "2025-12-06T13:24:19.417Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f0/578ee4ffce5818017de4fdf544e066c225bc435e73eb4793cde28a689d0b/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0e71a4db76726bf830b47477e7d830a75c01b2e9b01842e787a0836b0ba741e3", size = 63636, upload-time = "2025-12-06T13:24:20.497Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ad/8ae94814bf20159ea06310b742433e53d5820aa564c9fdf65bf2d79f8799/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2ba7799ec88540acd9861b10551d24656ca3c2888ecf4dba2ee0a71544a8923f", size = 56193, upload-time = "2025-12-06T13:24:21.559Z" }, - { url = "https://files.pythonhosted.org/packages/d1/31/6438cfcc3d3f0fa84d229fa125c243d5094e72628e525dfefadf3bcc6761/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2860299e4c74315f5951f0cf3e72ba0f201c3356c8a68f95a3ab4e620baf44e9", size = 72655, upload-time = "2025-12-06T13:24:22.673Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0d/2bbc9e9c3fc12ba8a6e261482f03a544aca524f92eae0b4908c0a10ba481/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:bb06015db9151f0c66c10aae8e3603adab6b6cd7d1f7335a858161d92fc29618", size = 62471, upload-time = "2025-12-06T13:24:23.8Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0b/34d491e7f49c1dbdb322ea8da6adecda7c7cd70b6644557c6e4ca5c6f7c7/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:242512a070817272865d37c8909059f43003b81da31f616bb0c391ceadffe067", size = 58119, upload-time = "2025-12-06T13:24:24.994Z" }, - { url = "https://files.pythonhosted.org/packages/ce/17/c21d0cde2a6c766923ae388fc1f78291e1564b0d38c814b5ea8a0e5e081c/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5d8277554a12d3e3eed6180ebda62786bf9fc8d7bb1ee00244258f4a87ca8d20", size = 60791, upload-time = "2025-12-06T13:24:26.046Z" }, - { url = "https://files.pythonhosted.org/packages/92/b2/eaa67038916a48de12b16f4c384bcc1b84b7ec731b23613cb05f27673294/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f40b7ddd698fc1e13a4b64fbe405e4e0e1279e8197e37050e24154655f5f7c4e", size = 74701, upload-time = "2025-12-06T13:24:27.466Z" }, - { url = "https://files.pythonhosted.org/packages/42/10/abb7757c330bb869ebb95dab0c57edf5961ffbd6c095c8209cbbf75d117d/pybase64-1.4.3-cp313-cp313t-win32.whl", hash = "sha256:46d75c9387f354c5172582a9eaae153b53a53afeb9c19fcf764ea7038be3bd8b", size = 33965, upload-time = "2025-12-06T13:24:28.548Z" }, - { url = "https://files.pythonhosted.org/packages/63/a0/2d4e5a59188e9e6aed0903d580541aaea72dcbbab7bf50fb8b83b490b6c3/pybase64-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:d7344625591d281bec54e85cbfdab9e970f6219cac1570f2aa140b8c942ccb81", size = 36207, upload-time = "2025-12-06T13:24:29.646Z" }, - { url = "https://files.pythonhosted.org/packages/1f/05/95b902e8f567b4d4b41df768ccc438af618f8d111e54deaf57d2df46bd76/pybase64-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:28a3c60c55138e0028313f2eccd321fec3c4a0be75e57a8d3eb883730b1b0880", size = 31505, upload-time = "2025-12-06T13:24:30.687Z" }, - { url = "https://files.pythonhosted.org/packages/e4/80/4bd3dff423e5a91f667ca41982dc0b79495b90ec0c0f5d59aca513e50f8c/pybase64-1.4.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:015bb586a1ea1467f69d57427abe587469392215f59db14f1f5c39b52fdafaf5", size = 33835, upload-time = "2025-12-06T13:24:31.767Z" }, - { url = "https://files.pythonhosted.org/packages/45/60/a94d94cc1e3057f602e0b483c9ebdaef40911d84a232647a2fe593ab77bb/pybase64-1.4.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:d101e3a516f837c3dcc0e5a0b7db09582ebf99ed670865223123fb2e5839c6c0", size = 40673, upload-time = "2025-12-06T13:24:32.82Z" }, - { url = "https://files.pythonhosted.org/packages/e3/71/cf62b261d431857e8e054537a5c3c24caafa331de30daede7b2c6c558501/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8f183ac925a48046abe047360fe3a1b28327afb35309892132fe1915d62fb282", size = 30939, upload-time = "2025-12-06T13:24:34.001Z" }, - { url = "https://files.pythonhosted.org/packages/24/3e/d12f92a3c1f7c6ab5d53c155bff9f1084ba997a37a39a4f781ccba9455f3/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30bf3558e24dcce4da5248dcf6d73792adfcf4f504246967e9db155be4c439ad", size = 31401, upload-time = "2025-12-06T13:24:35.11Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3d/9c27440031fea0d05146f8b70a460feb95d8b4e3d9ca8f45c972efb4c3d3/pybase64-1.4.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a674b419de318d2ce54387dd62646731efa32b4b590907800f0bd40675c1771d", size = 38075, upload-time = "2025-12-06T13:24:36.53Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d4/6c0e0cf0efd53c254173fbcd84a3d8fcbf5e0f66622473da425becec32a5/pybase64-1.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:720104fd7303d07bac302be0ff8f7f9f126f2f45c1edb4f48fdb0ff267e69fe1", size = 38257, upload-time = "2025-12-06T13:24:38.049Z" }, - { url = "https://files.pythonhosted.org/packages/50/eb/27cb0b610d5cd70f5ad0d66c14ad21c04b8db930f7139818e8fbdc14df4d/pybase64-1.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:83f1067f73fa5afbc3efc0565cecc6ed53260eccddef2ebe43a8ce2b99ea0e0a", size = 31685, upload-time = "2025-12-06T13:24:40.327Z" }, - { url = "https://files.pythonhosted.org/packages/db/26/b136a4b65e5c94ff06217f7726478df3f31ab1c777c2c02cf698e748183f/pybase64-1.4.3-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b51204d349a4b208287a8aa5b5422be3baa88abf6cc8ff97ccbda34919bbc857", size = 68460, upload-time = "2025-12-06T13:24:41.735Z" }, - { url = "https://files.pythonhosted.org/packages/68/6d/84ce50e7ee1ae79984d689e05a9937b2460d4efa1e5b202b46762fb9036c/pybase64-1.4.3-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30f2fd53efecbdde4bdca73a872a68dcb0d1bf8a4560c70a3e7746df973e1ef3", size = 71688, upload-time = "2025-12-06T13:24:42.908Z" }, - { url = "https://files.pythonhosted.org/packages/e3/57/6743e420416c3ff1b004041c85eb0ebd9c50e9cf05624664bfa1dc8b5625/pybase64-1.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0932b0c5cfa617091fd74f17d24549ce5de3628791998c94ba57be808078eeaf", size = 60040, upload-time = "2025-12-06T13:24:44.37Z" }, - { url = "https://files.pythonhosted.org/packages/3b/68/733324e28068a89119af2921ce548e1c607cc5c17d354690fc51c302e326/pybase64-1.4.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:acb61f5ab72bec808eb0d4ce8b87ec9f38d7d750cb89b1371c35eb8052a29f11", size = 56478, upload-time = "2025-12-06T13:24:45.815Z" }, - { url = "https://files.pythonhosted.org/packages/b5/9e/f3f4aa8cfe3357a3cdb0535b78eb032b671519d3ecc08c58c4c6b72b5a91/pybase64-1.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:2bc2d5bc15168f5c04c53bdfe5a1e543b2155f456ed1e16d7edce9ce73842021", size = 59463, upload-time = "2025-12-06T13:24:46.938Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d1/53286038e1f0df1cf58abcf4a4a91b0f74ab44539c2547b6c31001ddd054/pybase64-1.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8a7bc3cd23880bdca59758bcdd6f4ef0674f2393782763910a7466fab35ccb98", size = 60360, upload-time = "2025-12-06T13:24:48.039Z" }, - { url = "https://files.pythonhosted.org/packages/00/9a/5cc6ce95db2383d27ff4d790b8f8b46704d360d701ab77c4f655bcfaa6a7/pybase64-1.4.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ad15acf618880d99792d71e3905b0e2508e6e331b76a1b34212fa0f11e01ad28", size = 54999, upload-time = "2025-12-06T13:24:49.547Z" }, - { url = "https://files.pythonhosted.org/packages/64/e7/c3c1d09c3d7ae79e3aa1358c6d912d6b85f29281e47aa94fc0122a415a2f/pybase64-1.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448158d417139cb4851200e5fee62677ae51f56a865d50cda9e0d61bda91b116", size = 58736, upload-time = "2025-12-06T13:24:50.641Z" }, - { url = "https://files.pythonhosted.org/packages/db/d5/0baa08e3d8119b15b588c39f0d39fd10472f0372e3c54ca44649cbefa256/pybase64-1.4.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9058c49b5a2f3e691b9db21d37eb349e62540f9f5fc4beabf8cbe3c732bead86", size = 52298, upload-time = "2025-12-06T13:24:51.791Z" }, - { url = "https://files.pythonhosted.org/packages/00/87/fc6f11474a1de7e27cd2acbb8d0d7508bda3efa73dfe91c63f968728b2a3/pybase64-1.4.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ce561724f6522907a66303aca27dce252d363fcd85884972d348f4403ba3011a", size = 69049, upload-time = "2025-12-06T13:24:53.253Z" }, - { url = "https://files.pythonhosted.org/packages/69/9d/7fb5566f669ac18b40aa5fc1c438e24df52b843c1bdc5da47d46d4c1c630/pybase64-1.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:63316560a94ac449fe86cb8b9e0a13714c659417e92e26a5cbf085cd0a0c838d", size = 57952, upload-time = "2025-12-06T13:24:54.342Z" }, - { url = "https://files.pythonhosted.org/packages/de/cc/ceb949232dbbd3ec4ee0190d1df4361296beceee9840390a63df8bc31784/pybase64-1.4.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7ecd796f2ac0be7b73e7e4e232b8c16422014de3295d43e71d2b19fd4a4f5368", size = 54484, upload-time = "2025-12-06T13:24:55.774Z" }, - { url = "https://files.pythonhosted.org/packages/a7/69/659f3c8e6a5d7b753b9c42a4bd9c42892a0f10044e9c7351a4148d413a33/pybase64-1.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d01e102a12fb2e1ed3dc11611c2818448626637857ec3994a9cf4809dfd23477", size = 56542, upload-time = "2025-12-06T13:24:57Z" }, - { url = "https://files.pythonhosted.org/packages/85/2c/29c9e6c9c82b72025f9676f9e82eb1fd2339ad038cbcbf8b9e2ac02798fc/pybase64-1.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ebff797a93c2345f22183f454fd8607a34d75eca5a3a4a969c1c75b304cee39d", size = 71045, upload-time = "2025-12-06T13:24:58.179Z" }, - { url = "https://files.pythonhosted.org/packages/b9/84/5a3dce8d7a0040a5c0c14f0fe1311cd8db872913fa04438071b26b0dac04/pybase64-1.4.3-cp314-cp314-win32.whl", hash = "sha256:28b2a1bb0828c0595dc1ea3336305cd97ff85b01c00d81cfce4f92a95fb88f56", size = 34200, upload-time = "2025-12-06T13:24:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/57/bc/ce7427c12384adee115b347b287f8f3cf65860b824d74fe2c43e37e81c1f/pybase64-1.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:33338d3888700ff68c3dedfcd49f99bfc3b887570206130926791e26b316b029", size = 36323, upload-time = "2025-12-06T13:25:01.708Z" }, - { url = "https://files.pythonhosted.org/packages/9a/1b/2b8ffbe9a96eef7e3f6a5a7be75995eebfb6faaedc85b6da6b233e50c778/pybase64-1.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:62725669feb5acb186458da2f9353e88ae28ef66bb9c4c8d1568b12a790dfa94", size = 31584, upload-time = "2025-12-06T13:25:02.801Z" }, - { url = "https://files.pythonhosted.org/packages/ac/d8/6824c2e6fb45b8fa4e7d92e3c6805432d5edc7b855e3e8e1eedaaf6efb7c/pybase64-1.4.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:153fe29be038948d9372c3e77ae7d1cab44e4ba7d9aaf6f064dbeea36e45b092", size = 38601, upload-time = "2025-12-06T13:25:04.222Z" }, - { url = "https://files.pythonhosted.org/packages/ea/e5/10d2b3a4ad3a4850be2704a2f70cd9c0cf55725c8885679872d3bc846c67/pybase64-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7fe3decaa7c4a9e162327ec7bd81ce183d2b16f23c6d53b606649c6e0203e9e", size = 32078, upload-time = "2025-12-06T13:25:05.362Z" }, - { url = "https://files.pythonhosted.org/packages/43/04/8b15c34d3c2282f1c1b0850f1113a249401b618a382646a895170bc9b5e7/pybase64-1.4.3-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a5ae04ea114c86eb1da1f6e18d75f19e3b5ae39cb1d8d3cd87c29751a6a22780", size = 72474, upload-time = "2025-12-06T13:25:06.434Z" }, - { url = "https://files.pythonhosted.org/packages/42/00/f34b4d11278f8fdc68bc38f694a91492aa318f7c6f1bd7396197ac0f8b12/pybase64-1.4.3-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1755b3dce3a2a5c7d17ff6d4115e8bee4a1d5aeae74469db02e47c8f477147da", size = 75706, upload-time = "2025-12-06T13:25:07.636Z" }, - { url = "https://files.pythonhosted.org/packages/bb/5d/71747d4ad7fe16df4c4c852bdbdeb1f2cf35677b48d7c34d3011a7a6ad3a/pybase64-1.4.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb852f900e27ffc4ec1896817535a0fa19610ef8875a096b59f21d0aa42ff172", size = 65589, upload-time = "2025-12-06T13:25:08.809Z" }, - { url = "https://files.pythonhosted.org/packages/49/b1/d1e82bd58805bb5a3a662864800bab83a83a36ba56e7e3b1706c708002a5/pybase64-1.4.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:9cf21ea8c70c61eddab3421fbfce061fac4f2fb21f7031383005a1efdb13d0b9", size = 60670, upload-time = "2025-12-06T13:25:10.04Z" }, - { url = "https://files.pythonhosted.org/packages/15/67/16c609b7a13d1d9fc87eca12ba2dce5e67f949eeaab61a41bddff843cbb0/pybase64-1.4.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:afff11b331fdc27692fc75e85ae083340a35105cea1a3c4552139e2f0e0d174f", size = 64194, upload-time = "2025-12-06T13:25:11.48Z" }, - { url = "https://files.pythonhosted.org/packages/3c/11/37bc724e42960f0106c2d33dc957dcec8f760c91a908cc6c0df7718bc1a8/pybase64-1.4.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9a5143df542c1ce5c1f423874b948c4d689b3f05ec571f8792286197a39ba02", size = 64984, upload-time = "2025-12-06T13:25:12.645Z" }, - { url = "https://files.pythonhosted.org/packages/6e/66/b2b962a6a480dd5dae3029becf03ea1a650d326e39bf1c44ea3db78bb010/pybase64-1.4.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:d62e9861019ad63624b4a7914dff155af1cc5d6d79df3be14edcaedb5fdad6f9", size = 58750, upload-time = "2025-12-06T13:25:13.848Z" }, - { url = "https://files.pythonhosted.org/packages/2b/15/9b6d711035e29b18b2e1c03d47f41396d803d06ef15b6c97f45b75f73f04/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:84cfd4d92668ef5766cc42a9c9474b88960ac2b860767e6e7be255c6fddbd34a", size = 63816, upload-time = "2025-12-06T13:25:15.356Z" }, - { url = "https://files.pythonhosted.org/packages/b4/21/e2901381ed0df62e2308380f30d9c4d87d6b74e33a84faed3478d33a7197/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:60fc025437f9a7c2cc45e0c19ed68ed08ba672be2c5575fd9d98bdd8f01dd61f", size = 56348, upload-time = "2025-12-06T13:25:16.559Z" }, - { url = "https://files.pythonhosted.org/packages/c4/16/3d788388a178a0407aa814b976fe61bfa4af6760d9aac566e59da6e4a8b4/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:edc8446196f04b71d3af76c0bd1fe0a45066ac5bffecca88adb9626ee28c266f", size = 72842, upload-time = "2025-12-06T13:25:18.055Z" }, - { url = "https://files.pythonhosted.org/packages/a6/63/c15b1f8bd47ea48a5a2d52a4ec61f037062932ea6434ab916107b58e861e/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e99f6fa6509c037794da57f906ade271f52276c956d00f748e5b118462021d48", size = 62651, upload-time = "2025-12-06T13:25:19.191Z" }, - { url = "https://files.pythonhosted.org/packages/bd/b8/f544a2e37c778d59208966d4ef19742a0be37c12fc8149ff34483c176616/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d94020ef09f624d841aa9a3a6029df8cf65d60d7a6d5c8687579fa68bd679b65", size = 58295, upload-time = "2025-12-06T13:25:20.822Z" }, - { url = "https://files.pythonhosted.org/packages/03/99/1fae8a3b7ac181e36f6e7864a62d42d5b1f4fa7edf408c6711e28fba6b4d/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f64ce70d89942a23602dee910dec9b48e5edf94351e1b378186b74fcc00d7f66", size = 60960, upload-time = "2025-12-06T13:25:22.099Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9e/cd4c727742345ad8384569a4466f1a1428f4e5cc94d9c2ab2f53d30be3fe/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8ea99f56e45c469818b9781903be86ba4153769f007ba0655fa3b46dc332803d", size = 74863, upload-time = "2025-12-06T13:25:23.442Z" }, - { url = "https://files.pythonhosted.org/packages/28/86/a236ecfc5b494e1e922da149689f690abc84248c7c1358f5605b8c9fdd60/pybase64-1.4.3-cp314-cp314t-win32.whl", hash = "sha256:343b1901103cc72362fd1f842524e3bb24978e31aea7ff11e033af7f373f66ab", size = 34513, upload-time = "2025-12-06T13:25:24.592Z" }, - { url = "https://files.pythonhosted.org/packages/56/ce/ca8675f8d1352e245eb012bfc75429ee9cf1f21c3256b98d9a329d44bf0f/pybase64-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:57aff6f7f9dea6705afac9d706432049642de5b01080d3718acc23af87c5af76", size = 36702, upload-time = "2025-12-06T13:25:25.72Z" }, - { url = "https://files.pythonhosted.org/packages/3b/30/4a675864877397179b09b720ee5fcb1cf772cf7bebc831989aff0a5f79c1/pybase64-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e906aa08d4331e799400829e0f5e4177e76a3281e8a4bc82ba114c6b30e405c9", size = 31904, upload-time = "2025-12-06T13:25:26.826Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7c/545fd4935a0e1ddd7147f557bf8157c73eecec9cffd523382fa7af2557de/pybase64-1.4.3-graalpy311-graalpy242_311_native-macosx_10_9_x86_64.whl", hash = "sha256:d27c1dfdb0c59a5e758e7a98bd78eaca5983c22f4a811a36f4f980d245df4611", size = 38393, upload-time = "2025-12-06T13:26:19.535Z" }, - { url = "https://files.pythonhosted.org/packages/c3/ca/ae7a96be9ddc96030d4e9dffc43635d4e136b12058b387fd47eb8301b60f/pybase64-1.4.3-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0f1a0c51d6f159511e3431b73c25db31095ee36c394e26a4349e067c62f434e5", size = 32109, upload-time = "2025-12-06T13:26:20.72Z" }, - { url = "https://files.pythonhosted.org/packages/bf/44/d4b7adc7bf4fd5b52d8d099121760c450a52c390223806b873f0b6a2d551/pybase64-1.4.3-graalpy311-graalpy242_311_native-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a492518f3078a4e3faaef310697d21df9c6bc71908cebc8c2f6fbfa16d7d6b1f", size = 43227, upload-time = "2025-12-06T13:26:21.845Z" }, - { url = "https://files.pythonhosted.org/packages/08/86/2ba2d8734ef7939debeb52cf9952e457ba7aa226cae5c0e6dd631f9b851f/pybase64-1.4.3-graalpy311-graalpy242_311_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae1a0f47784fd16df90d8acc32011c8d5fcdd9ab392c9ec49543e5f6a9c43a4", size = 35804, upload-time = "2025-12-06T13:26:23.149Z" }, - { url = "https://files.pythonhosted.org/packages/4f/5b/19c725dc3aaa6281f2ce3ea4c1628d154a40dd99657d1381995f8096768b/pybase64-1.4.3-graalpy311-graalpy242_311_native-win_amd64.whl", hash = "sha256:03cea70676ffbd39a1ab7930a2d24c625b416cacc9d401599b1d29415a43ab6a", size = 35880, upload-time = "2025-12-06T13:26:24.663Z" }, - { url = "https://files.pythonhosted.org/packages/17/45/92322aec1b6979e789b5710f73c59f2172bc37c8ce835305434796824b7b/pybase64-1.4.3-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:2baaa092f3475f3a9c87ac5198023918ea8b6c125f4c930752ab2cbe3cd1d520", size = 38746, upload-time = "2025-12-06T13:26:25.869Z" }, - { url = "https://files.pythonhosted.org/packages/11/94/f1a07402870388fdfc2ecec0c718111189732f7d0f2d7fe1386e19e8fad0/pybase64-1.4.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:cde13c0764b1af07a631729f26df019070dad759981d6975527b7e8ecb465b6c", size = 32573, upload-time = "2025-12-06T13:26:27.792Z" }, - { url = "https://files.pythonhosted.org/packages/fa/8f/43c3bb11ca9bacf81cb0b7a71500bb65b2eda6d5fe07433c09b543de97f3/pybase64-1.4.3-graalpy312-graalpy250_312_native-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5c29a582b0ea3936d02bd6fe9bf674ab6059e6e45ab71c78404ab2c913224414", size = 43461, upload-time = "2025-12-06T13:26:28.906Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4c/2a5258329200be57497d3972b5308558c6de42e3749c6cc2aa1cbe34b25a/pybase64-1.4.3-graalpy312-graalpy250_312_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6b664758c804fa919b4f1257aa8cf68e95db76fc331de5f70bfc3a34655afe1", size = 36058, upload-time = "2025-12-06T13:26:30.092Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6d/41faa414cde66ec023b0ca8402a8f11cb61731c3dc27c082909cbbd1f929/pybase64-1.4.3-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:f7537fa22ae56a0bf51e4b0ffc075926ad91c618e1416330939f7ef366b58e3b", size = 36231, upload-time = "2025-12-06T13:26:31.656Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/160dded493c00d3376d4ad0f38a2119c5345de4a6693419ad39c3565959b/pybase64-1.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:277de6e03cc9090fb359365c686a2a3036d23aee6cd20d45d22b8c89d1247f17", size = 37939, upload-time = "2025-12-06T13:26:41.014Z" }, - { url = "https://files.pythonhosted.org/packages/b7/b8/a0f10be8d648d6f8f26e560d6e6955efa7df0ff1e009155717454d76f601/pybase64-1.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab1dd8b1ed2d1d750260ed58ab40defaa5ba83f76a30e18b9ebd5646f6247ae5", size = 31466, upload-time = "2025-12-06T13:26:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/d3/22/832a2f9e76cdf39b52e01e40d8feeb6a04cf105494f2c3e3126d0149717f/pybase64-1.4.3-pp311-pypy311_pp73-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:bd4d2293de9fd212e294c136cec85892460b17d24e8c18a6ba18750928037750", size = 40681, upload-time = "2025-12-06T13:26:43.782Z" }, - { url = "https://files.pythonhosted.org/packages/12/d7/6610f34a8972415fab3bb4704c174a1cc477bffbc3c36e526428d0f3957d/pybase64-1.4.3-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af6d0d3a691911cc4c9a625f3ddcd3af720738c21be3d5c72de05629139d393", size = 41294, upload-time = "2025-12-06T13:26:44.936Z" }, - { url = "https://files.pythonhosted.org/packages/64/25/ed24400948a6c974ab1374a233cb7e8af0a5373cea0dd8a944627d17c34a/pybase64-1.4.3-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5cfc8c49a28322d82242088378f8542ce97459866ba73150b062a7073e82629d", size = 35447, upload-time = "2025-12-06T13:26:46.098Z" }, - { url = "https://files.pythonhosted.org/packages/ee/2b/e18ee7c5ee508a82897f021c1981533eca2940b5f072fc6ed0906c03a7a7/pybase64-1.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:debf737e09b8bf832ba86f5ecc3d3dbd0e3021d6cd86ba4abe962d6a5a77adb3", size = 36134, upload-time = "2025-12-06T13:26:47.35Z" }, -] - [[package]] name = "pycparser" version = "3.0" @@ -3644,60 +1745,6 @@ crypto = [ { name = "cryptography" }, ] -[[package]] -name = "pymupdf" -version = "1.27.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/32/708bedc9dde7b328d45abbc076091769d44f2f24ad151ad92d56a6ec142b/pymupdf-1.27.2.3.tar.gz", hash = "sha256:7a92faa25129e8bbec5e50eeb9214f187665428c31b05c4ef6e36c58c0b1c6d2", size = 85759618, upload-time = "2026-04-24T14:13:14.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/09/ddbdfa7ee91fbabd6f63d7d744884cbdfe3e7ff9b8604749fb38bddf5c5d/pymupdf-1.27.2.3-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc1bc3cae6e9e150b0dbb0a9221bdfd411d65f0db2fe359eaa22467d7cc2a05f", size = 24002636, upload-time = "2026-04-24T14:09:17.459Z" }, - { url = "https://files.pythonhosted.org/packages/01/89/3f8edd6c4f50ca370e2a2f2a3011face36f3760728ffe76dffec91c0fca0/pymupdf-1.27.2.3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:660d93cb6da5bbddf11d3982ae27745dd3a9902d9f24cdb69adab83962294b5a", size = 23278238, upload-time = "2026-04-24T14:09:32.882Z" }, - { url = "https://files.pythonhosted.org/packages/c3/26/b7e5a70eb83bd189f8b5df87ec442746b992f2f632662839b288170d357d/pymupdf-1.27.2.3-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1dd460a3ae4597a755f00a3bd9771f5ebf1531dc111f6a36bf05dd00a6b84425", size = 24333923, upload-time = "2026-04-24T14:09:47.341Z" }, - { url = "https://files.pythonhosted.org/packages/e4/a0/aa1ee2240f29481a04a827c313333b4ecd8a14d6ac3e15d3f41a30574781/pymupdf-1.27.2.3-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:857842b4888827bd6155a1131341b2822a7ebe9a8c15a975fd7d490d7a64a30c", size = 24963198, upload-time = "2026-04-24T14:10:07.408Z" }, - { url = "https://files.pythonhosted.org/packages/69/49/4f742451f980840829fc00ba158bebb25d389c846d8f4f8c65936ee55de8/pymupdf-1.27.2.3-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:580983849c64a08d08344ca3d1580e87c01f046a8392421797bc850efd72a5b6", size = 25184609, upload-time = "2026-04-24T14:10:22.911Z" }, - { url = "https://files.pythonhosted.org/packages/f6/3f/3853d6608f394faf6eec2bd4e8ea9f6a00beea329b071abdb29f4164cc3d/pymupdf-1.27.2.3-cp310-abi3-win32.whl", hash = "sha256:a5c1088a87189891a4946ab314a14b7934ac4c5b6077f7e74ebee956f8906d0e", size = 18019286, upload-time = "2026-04-24T14:10:34.239Z" }, - { url = "https://files.pythonhosted.org/packages/44/47/5fb10fe73f96b31253a41647c362ea9e0380920bddf16028414a051247fc/pymupdf-1.27.2.3-cp310-abi3-win_amd64.whl", hash = "sha256:d20f68ef15195e073071dbc4ae7455257c7889af7584e39df490c0a92728526e", size = 19249102, upload-time = "2026-04-24T14:10:46.72Z" }, - { url = "https://files.pythonhosted.org/packages/53/a4/b9e91aac82293f9c954654c85581ee8212b5b05efadc534b581141241e6f/pymupdf-1.27.2.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:77691604c5d1d0233827139bbcdea61fd57879c84712b8e49b1f45520f7ab9c2", size = 25000393, upload-time = "2026-04-24T14:11:01.669Z" }, -] - -[[package]] -name = "pypdfium2" -version = "5.10.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/78/d9b45abb97a3686643f7c6472a5f7688f2013a373226121dc76b9debbacf/pypdfium2-5.10.1.tar.gz", hash = "sha256:f257d2011eb43c846b7e9f5a802e28646b29732763e4a35dd6ca76f9be580538", size = 272963, upload-time = "2026-06-15T10:09:16.179Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/49/0b85fcc0d236582143a25cf275b0b4f5d786f51eb07a89ec9c79d43efe18/pypdfium2-5.10.1-py3-none-android_23_arm64_v8a.whl", hash = "sha256:13abf7a9f5e0ddebc8bbcccea5f13ae5abe8a298ea219e125b0fc24c1d2171b4", size = 3409176, upload-time = "2026-06-15T10:08:36.017Z" }, - { url = "https://files.pythonhosted.org/packages/20/d2/2f522c5b2ad5166edf256bb4dbab97de5d07b2573f8a5630ddfcd1f8d4ee/pypdfium2-5.10.1-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:a0dc52b56631e2f7edcdb22bae3b155aa840bec32c5bd05781e90baaecee88e7", size = 2866175, upload-time = "2026-06-15T10:08:37.955Z" }, - { url = "https://files.pythonhosted.org/packages/a7/be/477548c026c2badfdbf4afc3358b7135121fb5bec2e2effcb67e3a674d0b/pypdfium2-5.10.1-py3-none-macosx_12_0_arm64.whl", hash = "sha256:ebb9e63f92d15fc41b359fe7a187233dfae37548800e1fa09cb2fc466ac89951", size = 3621427, upload-time = "2026-06-15T10:08:39.969Z" }, - { url = "https://files.pythonhosted.org/packages/8c/9b/0131c7f711b62c6edd6b200e9eb6340be6de4f6dc5baae625e3394d8d5fb/pypdfium2-5.10.1-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:d04f2050b6b32bb18624688b600543342a4ab3aacf64bf66521a5af72bbc7de1", size = 3682825, upload-time = "2026-06-15T10:08:42.181Z" }, - { url = "https://files.pythonhosted.org/packages/9d/3d/bddfceb6e67e54d6dd1ab6c0f1feff796a89596e40f6345a3b4cc6a3d408/pypdfium2-5.10.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0de53d2710ca9509fc2812340acc57c3f043697609068592d87de654f8cabf44", size = 3682206, upload-time = "2026-06-15T10:08:44.087Z" }, - { url = "https://files.pythonhosted.org/packages/58/08/dedeb25c6645fc8a5eda24f54e0b1d083b7334ababf5f518bb939d729cc0/pypdfium2-5.10.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:10a26ce04795f8ec079e81c707fcb5737061e8a78025babc3d6e36642e9c903a", size = 3413720, upload-time = "2026-06-15T10:08:45.739Z" }, - { url = "https://files.pythonhosted.org/packages/cc/48/69e1fc8b1216005243c6415183bbf6de1cda3f5a06758b3fa4a26a7385c6/pypdfium2-5.10.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be6d2a8d1bcfd777188e7aa55a25c83f34d54e8350ad8810fb32017787d9b0d9", size = 3812913, upload-time = "2026-06-15T10:08:47.45Z" }, - { url = "https://files.pythonhosted.org/packages/72/7f/132455a58ad736d76815c6cd1307532c3f433299d945b9d2f8cc2387c309/pypdfium2-5.10.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf4d2527f79f31c550490cc74c9f32e19385a944630a3ef4cd4d9b6f961fbf77", size = 4223220, upload-time = "2026-06-15T10:08:49.099Z" }, - { url = "https://files.pythonhosted.org/packages/bf/8c/4d5804eca598bbe894e0a9a510807e221c1623e7276ce6b68fa2660dc933/pypdfium2-5.10.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ba127750bc3f4161461538d532d74491cd976f584f1753a6cee9cb821338ec1", size = 3738950, upload-time = "2026-06-15T10:08:50.821Z" }, - { url = "https://files.pythonhosted.org/packages/d1/7f/baac59bf14ff914d97789ee0368c22ae233aa857aa9c0726bcb515dbc4d7/pypdfium2-5.10.1-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:80f30517ee089dfbbc6e9de6da365b6e8c0ce8f80c40b7d4025a3b1d3bbd8a70", size = 4029869, upload-time = "2026-06-15T10:08:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d7/a5d58a0bcba31a0e37ed636a76ef3d2d215733f28af61637287d061b0c54/pypdfium2-5.10.1-py3-none-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ad5f5de15febb788c6eb3853e58aceda9cd8c5187c92472abaeecf9558deb0cf", size = 3990927, upload-time = "2026-06-15T10:08:54.555Z" }, - { url = "https://files.pythonhosted.org/packages/da/1a/98eebd14b36812176297cf765d504ebeebc982f895c8a3a9fbd2717797de/pypdfium2-5.10.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8947fa3cd808da33960bdf8ff9e5247aa94ccd94f0b09bb3402e99498d83bcc9", size = 4989624, upload-time = "2026-06-15T10:08:56.284Z" }, - { url = "https://files.pythonhosted.org/packages/1c/20/f2e124d607b8bb90a9f1ce976afff38c70e215cd7ea86af784cb2e8a19dd/pypdfium2-5.10.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:139a6387a3a2652f288e53164268bb03af4fa221d78484ee18407053a60082a3", size = 4535124, upload-time = "2026-06-15T10:08:58.214Z" }, - { url = "https://files.pythonhosted.org/packages/59/ef/469ea87f668a32ff3280ea15e522e3a7858d2c80f1ecc320ece1244624c9/pypdfium2-5.10.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:172ff3e10358d66456e27fb0b8b5098e28ec24e51072eb4b5b86077d550e21bb", size = 5229373, upload-time = "2026-06-15T10:09:00.702Z" }, - { url = "https://files.pythonhosted.org/packages/c3/48/c7ed3001f0c5e28114c98bf918c8121e422a65ba7323e1e12f3f28e2d278/pypdfium2-5.10.1-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:09fda0609dc4749c9865a0315c447d6f2583a580693bcabd69980d3fbb22ad51", size = 5140010, upload-time = "2026-06-15T10:09:02.734Z" }, - { url = "https://files.pythonhosted.org/packages/b2/bc/00b731bfc1fdc0f3c7d91108fceb54978ff4f5a92336820e5ed6c12535ff/pypdfium2-5.10.1-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:6b01adcfe9aaf7a635a59bed5687fd4fd7b0da292664f050d4ebd2bfa5c70584", size = 4643310, upload-time = "2026-06-15T10:09:04.794Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e8/3ad242233f657c19092a8c83684c8154b8d02a826535a6a2591d91aa5dde/pypdfium2-5.10.1-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:610e14c37d2090b826dccc0604fc7e7612c0ce591190780ae228abdb9abb971e", size = 5087879, upload-time = "2026-06-15T10:09:06.861Z" }, - { url = "https://files.pythonhosted.org/packages/80/3f/9d0747a02ac3021ed3db7ac27c5187d97e78b0253a4bdfbf386666968474/pypdfium2-5.10.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f2803020952afa57e1e148adc19369b835154e1fa251a1ca85008ab6466a710f", size = 5047369, upload-time = "2026-06-15T10:09:08.883Z" }, - { url = "https://files.pythonhosted.org/packages/9e/41/7d5187e9527eae81890a3b13193442112ef788d61ddaa68a6d59ac447bec/pypdfium2-5.10.1-py3-none-win32.whl", hash = "sha256:8702bb4f01ddfc8e7757b41b4c2c8392ac17c9f0234476e1e69672ea7c6d6aa0", size = 3680056, upload-time = "2026-06-15T10:09:10.95Z" }, - { url = "https://files.pythonhosted.org/packages/16/1d/c62bd59dd8345cc4b640f942f465633f6b07b859d01ddb648610a7bf5c7c/pypdfium2-5.10.1-py3-none-win_amd64.whl", hash = "sha256:58da5b51fb7884c7d21a05062ab13edb011d1a08dfd9694f3d5d685df62796b9", size = 3812105, upload-time = "2026-06-15T10:09:12.684Z" }, - { url = "https://files.pythonhosted.org/packages/10/d5/21bac39125df8a93e99c04583486b58a62b5997d6b3541e3ad0f69053392/pypdfium2-5.10.1-py3-none-win_arm64.whl", hash = "sha256:e3301c2f7a66fb8cb57dba857d0c9e90215e178f6602a87c5a306cd98513dab8", size = 3600043, upload-time = "2026-06-15T10:09:14.606Z" }, -] - -[[package]] -name = "pypika" -version = "0.51.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/78/cbaebba88e05e2dcda13ca203131b38d3640219f20ebb49676d26714861b/pypika-0.51.1.tar.gz", hash = "sha256:c30c7c1048fbf056fd3920c5a2b88b0c29dd190a9b2bee971fd17e4abe4d0ebe", size = 80919, upload-time = "2026-02-04T11:27:48.304Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/57/83/c77dfeed04022e8930b08eedca2b6e5efed256ab3321396fde90066efb65/pypika-0.51.1-py2.py3-none-any.whl", hash = "sha256:77985b4d7ce71b9905255bf12468cf598349e98837c037541cfc240e528aec46", size = 60585, upload-time = "2026-02-04T11:27:46.251Z" }, -] - [[package]] name = "pyproject-api" version = "1.10.1" @@ -3710,15 +1757,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/d7/29e1e5e882f79133631f7bcace42d23db493f616463c157a1ab614bf69dd/pyproject_api-1.10.1-py3-none-any.whl", hash = "sha256:fa9e6f66c35b5017e909825d8f2b5d5482ea699d7be809d21c03bd1f7317f36a", size = 12992, upload-time = "2026-05-28T14:22:12.711Z" }, ] -[[package]] -name = "pyproject-hooks" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, -] - [[package]] name = "pytest" version = "9.1.1" @@ -3855,19 +1893,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, ] -[[package]] -name = "python-docx" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "lxml" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256, upload-time = "2025-06-16T20:46:27.921Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987, upload-time = "2025-06-16T20:46:22.506Z" }, -] - [[package]] name = "python-dotenv" version = "1.2.2" @@ -3886,15 +1911,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, ] -[[package]] -name = "pytube" -version = "15.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/e7/16fec46c8d255c4bbc4b185d89c91dc92cdb802836570d8004d0db169c91/pytube-15.0.0.tar.gz", hash = "sha256:076052efe76f390dfa24b1194ff821d4e86c17d41cb5562f3a276a8bcbfc9d1d", size = 67229, upload-time = "2023-05-07T19:39:01.903Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/64/bcf8632ed2b7a36bbf84a0544885ffa1d0b4bcf25cc0903dba66ec5fdad9/pytube-15.0.0-py3-none-any.whl", hash = "sha256:07b9904749e213485780d7eb606e5e5b8e4341aa4dccf699160876da00e12d78", size = 57594, upload-time = "2023-05-07T19:38:59.191Z" }, -] - [[package]] name = "pywin32" version = "312" @@ -3986,110 +2002,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] -[[package]] -name = "regex" -version = "2026.5.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/0e/49aee608ad09480e7fd276898c99ec6192985fa331abe4eb3a986094490b/regex-2026.5.9.tar.gz", hash = "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270", size = 416074, upload-time = "2026-05-09T23:15:19.37Z" } -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/03/d2/59f01110660081cce9c0bc30ebd0b5ee250dacf658e3248ed92f01e0e8ee/regex-2026.5.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46f1326ca6e65b0879d23ca302c0f2415aad42ff0309b9c818e7949fe19a41d8", size = 291271, upload-time = "2026-05-09T23:12:07.731Z" }, - { 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/ce/fc/294fe4fac4f2ed67207b17471815870c1c45b3a489e08e0ac96daea16ef6/regex-2026.5.9-cp311-cp311-win32.whl", hash = "sha256:8676474c07469d6f33dd1085ca2cd45f65785f32518f2b20e36d9953ca07f994", size = 266249, upload-time = "2026-05-09T23:12:30.141Z" }, - { url = "https://files.pythonhosted.org/packages/d0/b0/8dce459f6245bcf8f6e9f23ac9569f1a0f15c131cc0745e82b43226204cf/regex-2026.5.9-cp311-cp311-win_amd64.whl", hash = "sha256:246de9d60aa3f8538b519834dd95cbf276ea263d6a7bd5a3666dc3fa0230505b", size = 278423, upload-time = "2026-05-09T23:12:31.676Z" }, - { url = "https://files.pythonhosted.org/packages/db/8d/f9aeff6ad63a3ef720386f2907e6d34a35a510a6e498ebad28b0fb3f6ab6/regex-2026.5.9-cp311-cp311-win_arm64.whl", hash = "sha256:d726ca3f0d76969bf1e8e477d160d3d666bbf999f6860bd314889e5345782046", size = 270420, upload-time = "2026-05-09T23:12:33.194Z" }, - { 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/1e/95/fc7ba4303b5a0f92446a12ee6778ef2c6c799233f5060042a31bf390cfe9/regex-2026.5.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:398c521292f4c7fb807001dcd54694d3a1fcafc179a36ad9cc56f98df85930b6", size = 292112, upload-time = "2026-05-09T23:12:36.285Z" }, - { 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/c3/1c/bdcc98f9a4af4fdd166c74941174619ccff4726d3ce32faa8e9a2ecd38dd/regex-2026.5.9-cp312-cp312-win32.whl", hash = "sha256:164eba9b755ea6f244b0d881196fbc1fac09714e9782c9e2732b813142033c8e", size = 266699, upload-time = "2026-05-09T23:12:59.14Z" }, - { url = "https://files.pythonhosted.org/packages/78/87/240d36864f9e48ace85f72e79ced97ceb7f27ce87739a947dcb834b4e6bc/regex-2026.5.9-cp312-cp312-win_amd64.whl", hash = "sha256:86f40a5d6444db30a125c9c9177e6b25dad981cbc37451fd838f145e6edac92e", size = 277783, upload-time = "2026-05-09T23:13:00.789Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b5/7b30f312b0669dff5beebe5b0989dc2d1a312b1a44fab852199c387a5b96/regex-2026.5.9-cp312-cp312-win_arm64.whl", hash = "sha256:96f5f58b54a063d7ea9dca08e1cf57bfe10499c4d579ee672da284f57f5f0070", size = 270513, upload-time = "2026-05-09T23:13:02.426Z" }, - { 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/44/da/bf30abaaa737b58f4a4b8c4a03659e02fd92092c822e0197ed9e0daab917/regex-2026.5.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d7bdc0ab8f3dd7e1b4f9ab88634e13374669db86bb3c72e8292f07ae313f539f", size = 292019, upload-time = "2026-05-09T23:13:06.022Z" }, - { 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/05/a4/018e71f7d2ad48c1ebe6d3ae0026f9b7cb4802fd15c7cc02fdf724355102/regex-2026.5.9-cp313-cp313-win32.whl", hash = "sha256:f3844f134e834076677dd369976e9f5068679fcb8e50102fdf6b7ac96a3ec127", size = 266691, upload-time = "2026-05-09T23:13:29.549Z" }, - { url = "https://files.pythonhosted.org/packages/e6/1d/861a93719fb9ee7dbfc3761b3797b7a3e112a5d42c6129459d2d741be9b5/regex-2026.5.9-cp313-cp313-win_amd64.whl", hash = "sha256:3527bb4942d2c14552155406cdedd906567456821848aed1cb4933a391bf5eca", size = 277747, upload-time = "2026-05-09T23:13:31.859Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c6/0a2436ae4da1ba76e51cb98943c6838a9a721faa40ebe2dce07694ae34e3/regex-2026.5.9-cp313-cp313-win_arm64.whl", hash = "sha256:56a33f191f17d8c417f99945ebdc1e691d3af9605d86ec68c7e54a57e3e17af6", size = 270500, upload-time = "2026-05-09T23:13:33.525Z" }, - { 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/c4/43/fd1177a2032037c681baecdb3422ee4e1424aec4e4f470ef47793d325274/regex-2026.5.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:48036f6374aaa79eb3b754ec29c61d1c6b1606749d705a13f8854fa2539671f6", size = 293952, upload-time = "2026-05-09T23:13:38.307Z" }, - { 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" }, - { url = "https://files.pythonhosted.org/packages/04/99/eff29f1037dcab36702c9ee5d6858cf1ce2336ea8ea2987f64245b99ea5e/regex-2026.5.9-cp313-cp313t-win32.whl", hash = "sha256:ed2c9e8068b614c574d8d30e543d617cf5379b0535d46f97ef00e904745a08b5", size = 269951, upload-time = "2026-05-09T23:14:03.661Z" }, - { url = "https://files.pythonhosted.org/packages/0e/9d/8870b8981d27b22cda77bb26a5ac7ebfa9c7d9e0dea195a834a82380e748/regex-2026.5.9-cp313-cp313t-win_amd64.whl", hash = "sha256:b46b0f094dc1d3b90356c85a0bd2c9bafc4a6a190b9d6f8ddd5a033b6e088ed4", size = 281240, upload-time = "2026-05-09T23:14:05.56Z" }, - { url = "https://files.pythonhosted.org/packages/72/b1/3379415e8f135c13ac551353397cc4fe97b4978f3cac73c5fcbcded548b8/regex-2026.5.9-cp313-cp313t-win_arm64.whl", hash = "sha256:872acc074bd29ffc9913ecdfedf6ea77502312ca44a4aa0d3779089c6069d8de", size = 272383, upload-time = "2026-05-09T23:14:07.843Z" }, - { url = "https://files.pythonhosted.org/packages/13/3e/9c3cd292d8808b3645a2ce517e200179b6d0e903f176300bd8b542e14de5/regex-2026.5.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:1bd7587a2948b4085195d5a3374eaf4a425dc3e55784c038175355ecf3bbbf8a", size = 490376, upload-time = "2026-05-09T23:14:09.64Z" }, - { url = "https://files.pythonhosted.org/packages/60/70/d43ee8a2ca0a8b68d167f21658b85520ac0574617c7f320367c5047f7556/regex-2026.5.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dea2e88e1cce4522496cce630e11e67b98b7076620bc4336c3f674bc21a375f4", size = 291964, upload-time = "2026-05-09T23:14:11.424Z" }, - { url = "https://files.pythonhosted.org/packages/21/91/9d50b433828d8e74196904e168a43abf1e6e88b2a15d47ed742456720c37/regex-2026.5.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2099f7e7ff7b6aa3192312650a56e91cc091e49d50b04e4f6f8b6e28b3b27f1c", size = 289682, upload-time = "2026-05-09T23:14:13.123Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/b835e3cafbb9d977736912436259ff551d60919f7d7b3d37d46659c63564/regex-2026.5.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecd353045824e4477562a2ac718c25799cdaaa41f7aa925a806a8a3e6848a5b9", size = 796996, upload-time = "2026-05-09T23:14:14.923Z" }, - { url = "https://files.pythonhosted.org/packages/2c/a6/9f992d00019166b9de01c546dd4549bc679f2a68df11b877740b0760b7c2/regex-2026.5.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65c8c8c37377794bd5b2f3ebe51919042bf17aec802e23c833d89782ed0c78af", size = 866089, upload-time = "2026-05-09T23:14:17.757Z" }, - { url = "https://files.pythonhosted.org/packages/e0/08/4d32af657e049b19cb62b02e46e38fe1518797bfb2203ee93a510b21b0dc/regex-2026.5.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b73ab8afcf66c622db143d1c6fda4e58e4d537ee4f125229ad47b1ab80f34c0", size = 911530, upload-time = "2026-05-09T23:14:20.353Z" }, - { url = "https://files.pythonhosted.org/packages/d9/27/2af43dd1dc201d1fecefda64a45f4ad0995855b92724f795a777b402ee69/regex-2026.5.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0de5cf193997384ed2ca6f1cd4f78055b255d93d82d5a8cd6ba0d11c10b167e4", size = 800643, upload-time = "2026-05-09T23:14:22.265Z" }, - { url = "https://files.pythonhosted.org/packages/a4/dd/23a249047013b5321d4a60c4d2437462086f601b061776a525e5fba2a59f/regex-2026.5.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d641a8c9a61618047796d572a39a79b26167b0411d2c3031937b2fe2d081e2cf", size = 777223, upload-time = "2026-05-09T23:14:24.179Z" }, - { url = "https://files.pythonhosted.org/packages/94/6a/e85ed9538cd19586d0465076a4578a12e093ce776d15f3f8ce92733a8dd6/regex-2026.5.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24b2355ef5cc9aa5b8f07d17704face1c166fdcc2290fa7bd6e6c925655a8346", size = 785760, upload-time = "2026-05-09T23:14:26.065Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c4/f25473209438638e947c55f9156fd8f236f74169229028cc99116380868e/regex-2026.5.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a24852d3c29ad9e47593593d8a247c44ccc3d0548ef12c822d6ed0810affe676", size = 860891, upload-time = "2026-05-09T23:14:28.17Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f7/f4f86e3c74419c37370e91f150ae0c2ef7d34b2e0e4cdd5da046a02e4022/regex-2026.5.9-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:916714069da19329ef7de197dcbc77bb3104145c7c2c864dbfbe318f46b88b14", size = 765891, upload-time = "2026-05-09T23:14:30.06Z" }, - { url = "https://files.pythonhosted.org/packages/26/70/704d8e13765939146b1cd0ef4e2feb71d7929727d2290f026eed10095955/regex-2026.5.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fa411799ca8da32a8d38d020a88faa5b6f91657d284761352940ecf9f7c3bbdd", size = 851380, upload-time = "2026-05-09T23:14:32.123Z" }, - { url = "https://files.pythonhosted.org/packages/26/29/1a13582a8460038edc38e49f64ceb0dd7c60f5caba77571f4bf6601965d9/regex-2026.5.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e6da47d679b7010ef27556b6e0f99771b744936db1792a10ceac6547ae1503e", size = 789350, upload-time = "2026-05-09T23:14:34.799Z" }, - { url = "https://files.pythonhosted.org/packages/73/56/3dcafe34fc72e271d62ad9a291801e88a1457bb251c132f15fcc2e5aad1a/regex-2026.5.9-cp314-cp314-win32.whl", hash = "sha256:98bd73080e8756255137e1bd3f3f00295bbc5aa383c0e0f973920e9134d7c4ad", size = 272130, upload-time = "2026-05-09T23:14:36.729Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9c/02eebf0be95efe416c664db7fb8b6b05b7a0b06a7544f2884f2558b0526f/regex-2026.5.9-cp314-cp314-win_amd64.whl", hash = "sha256:ff8d372ac2acdc048d1c19916f27ee61bc5722728458ba6ca5052f2c72d51763", size = 280999, upload-time = "2026-05-09T23:14:39.126Z" }, - { url = "https://files.pythonhosted.org/packages/70/5a/1dd1abee76cb7a846a0bcf42fdc87e5720c3c33c24f3e37814310a513d9f/regex-2026.5.9-cp314-cp314-win_arm64.whl", hash = "sha256:e1d93bf647916292e8edcec150c07ddf3dc50179ccaf770c04a7f9e452155372", size = 273500, upload-time = "2026-05-09T23:14:41.059Z" }, - { url = "https://files.pythonhosted.org/packages/86/c1/c5f619b0057a7965cb78ec559c1d7a45ce8c99a35bea95483d64959a93d9/regex-2026.5.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:83d0ee4a57d1c87cb549e195ec300b8f0ec3a82eba66d835e4e2ed8634fe4499", size = 494269, upload-time = "2026-05-09T23:14:42.869Z" }, - { url = "https://files.pythonhosted.org/packages/05/2c/5d01f1aee33de4bbe60c8452945bfc8477ca7c5ae4450f6bfe711036cb36/regex-2026.5.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d3d7eb5c9a7f6df82ed3cfac9beb93882a5cbcb5b8b157b56cb2b3b276574ac1", size = 293954, upload-time = "2026-05-09T23:14:44.822Z" }, - { url = "https://files.pythonhosted.org/packages/7a/fe/e8988b2ae2108c6ef71bd4aa8d87fbe257976dd0810e826cd75f701c68b6/regex-2026.5.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:075160bf16658e16d35233300b8453aac25de4cbea808d22348b6979668e924d", size = 292405, upload-time = "2026-05-09T23:14:47.211Z" }, - { url = "https://files.pythonhosted.org/packages/79/34/d2b0937faa7859263f7f0a3c6b103a1296306be6952dc173d0154e9a2f49/regex-2026.5.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45375819235558a4ff1c4971dc32881f022613abdb180128f5cb4768c1765a1c", size = 811855, upload-time = "2026-05-09T23:14:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/80/fe/daf53a47457a8486db66c66c01ceb9c2303eecee3f87197f1e77eb1a736d/regex-2026.5.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ead4b163ac30a29574510cd4b3e2e985ac5290c05fc7095557d6a5f403fc31b5", size = 871189, upload-time = "2026-05-09T23:14:51.555Z" }, - { url = "https://files.pythonhosted.org/packages/1c/75/058fc4470cbfbf57d800aff1a0022b929a3f9fa553ee10a0cdf2070eb31f/regex-2026.5.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c6e4218fbdfbcd4f6c19efca40930d24a621bf4b48cb76bc6640543bd28ef20", size = 917485, upload-time = "2026-05-09T23:14:53.633Z" }, - { url = "https://files.pythonhosted.org/packages/88/e7/179cfda3a28bc843b5c6cfe7f79f23489c791ed95f151083803660878432/regex-2026.5.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6351571c8a42b505eb555c0dc47d740d0fb66977dc142919eea6f4325b7c56a0", size = 816369, upload-time = "2026-05-09T23:14:56.198Z" }, - { url = "https://files.pythonhosted.org/packages/41/90/6f0cc422071688266d344fca8462d787cba0a2c144acb25721f9a61ec265/regex-2026.5.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:002205cafd2a9e78c6290c7d1df277bf3277b3b7a30e0b4bb0dac2e2e3f7cb2d", size = 785869, upload-time = "2026-05-09T23:14:58.602Z" }, - { url = "https://files.pythonhosted.org/packages/02/67/a31f1760f09c27b251ef39e9beb541f462cf977381d067faa764c2c0e393/regex-2026.5.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8abd33fef90b2a9efac5557d6033ca82d1195ed3a15fea5af15ba7b463c6a63b", size = 801427, upload-time = "2026-05-09T23:15:00.642Z" }, - { url = "https://files.pythonhosted.org/packages/e3/c4/1a80654597b6bc1e1ea0494824c31200e8a956abe290afae9b19a166a148/regex-2026.5.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:31037c82eccb44b7ea2e9e221d7c01429430e989a1f4b91ea5a855f6017b509a", size = 866482, upload-time = "2026-05-09T23:15:03.384Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/960724e06482c08466ff5611e242e86f80062949cdf6b4b9cc317b9dd93d/regex-2026.5.9-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5604dfd046dc37eca90250fc3be938b076c8059fa772ac0ed6f499b0f0fb0415", size = 773022, upload-time = "2026-05-09T23:15:05.625Z" }, - { url = "https://files.pythonhosted.org/packages/50/a8/a9979c3e7918280e93159ebcab5ef1a65116dd4f3bd6091be0eae4a126e8/regex-2026.5.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e1b1b4e496afbb24f4a62aba855ee4f88f25578927697b340702e48c9ee6bc2", size = 856642, upload-time = "2026-05-09T23:15:07.966Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d4/a9b732f2f0072c0ab12227483abb24fffcb9f73f8a2b203df0a6d0434735/regex-2026.5.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:be3372b9df6ddecff6486d37e19095a7b4973137caf5512407a89f4455361f41", size = 803552, upload-time = "2026-05-09T23:15:10.215Z" }, - { url = "https://files.pythonhosted.org/packages/d5/fe/1b3113817447a1d4155e4ac76d2e072f42c0bcba2f43fa8a0e756ea2cd91/regex-2026.5.9-cp314-cp314t-win32.whl", hash = "sha256:3ddd90103f9e5c471c49c7852ecc1fe27c7e45eb99e977aefe7caa4e779f4f58", size = 275746, upload-time = "2026-05-09T23:15:12.609Z" }, - { url = "https://files.pythonhosted.org/packages/92/73/93d42045302636c91f2e5ef588b65b84b01428f28ec77de256b1dfdfbe5c/regex-2026.5.9-cp314-cp314t-win_amd64.whl", hash = "sha256:ca518ed29c46eecba6010b15f1b9a479314d2de409536e71b6a13aa04e3b8a77", size = 285685, upload-time = "2026-05-09T23:15:15.086Z" }, - { url = "https://files.pythonhosted.org/packages/da/80/35b4c33c804a165a7f55289afda3ea9e3eb6d15800341a2d66455c0f1f30/regex-2026.5.9-cp314-cp314t-win_arm64.whl", hash = "sha256:5e41809d2683fcde7d5a8c87a6567ba1fb1ce0de9f31bff578de00a4b2d76daa", size = 275713, upload-time = "2026-05-09T23:15:16.98Z" }, -] - [[package]] name = "requests" version = "2.34.2" @@ -4105,19 +2017,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] -[[package]] -name = "requests-oauthlib" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "oauthlib" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, -] - [[package]] name = "requests-toolbelt" version = "1.0.0" @@ -4130,19 +2029,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, ] -[[package]] -name = "rich" -version = "14.3.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/67/cae617f1351490c25a4b8ac3b8b63a4dda609295d8222bad12242dfdc629/rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9", size = 230524, upload-time = "2026-04-11T02:57:45.419Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" }, -] - [[package]] name = "roman-numerals" version = "4.1.0" @@ -4338,15 +2224,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/5f/4c174edad94f82de888ac00a5ddd8d07b35609b6c94f0bdf4d74af57703e/s3transfer-0.19.0-py3-none-any.whl", hash = "sha256:777cc2415536f1debadb5c2ef7779275d0fc0fe0e042411cdd6caebeb2685262", size = 90101, upload-time = "2026-06-16T19:44:50.439Z" }, ] -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, -] - [[package]] name = "six" version = "1.17.0" @@ -4560,87 +2437,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, ] -[[package]] -name = "tiktoken" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "regex" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/e5/5f3cb2159769d0f4324c0e9e87f9de3c4b1cd45848a96b2eb3566ad5ca77/tiktoken-0.13.0.tar.gz", hash = "sha256:c9435714c3a84c2319499de9a300c0e604449dd0799ff246458b3bb6a7f433c1", size = 38986, upload-time = "2026-05-15T04:51:27.153Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/4c/1bc81f4cd53e827c4ee67ca951b5935724716049452d8dfa09b8b82372bb/tiktoken-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7bfe1849caa65d1e1d9871817170ec497bbb7984e182012e1bdce72f66608cdb", size = 1036353, upload-time = "2026-05-15T04:50:21.757Z" }, - { url = "https://files.pythonhosted.org/packages/75/91/10b9c7076bc02c246c853201fdbbe300a4b8c5ed7b84c25f7403f4e32655/tiktoken-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:91c180fe255bd5a86d8316210d2833a1d4d33d026cd86a67812f4773743c8d26", size = 984644, upload-time = "2026-05-15T04:50:23.256Z" }, - { url = "https://files.pythonhosted.org/packages/4e/e4/fceae98015fab47fcd49b8bd7f46145bcd187a47e0add1e5378ed67ef980/tiktoken-0.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:059c8ecf554eb5b41e6e054ba467b871b03277d267dee7244380aca4359747d4", size = 1119261, upload-time = "2026-05-15T04:50:24.348Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/fe42ad00de01a8c4a49ad8649a2c8a316835a9cad5961b11d21eac0020a5/tiktoken-0.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:36217497eaffc158607a3b26f065300db2aefd43b115263f3b9688ce38146173", size = 1138253, upload-time = "2026-05-15T04:50:25.505Z" }, - { url = "https://files.pythonhosted.org/packages/03/c4/ccee1ecccca107e9a16efcecdeeb964c325305038554d466ece65b42338f/tiktoken-0.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:303f7d91b4fce3baddbcde05c139091d4caa5026ac7214c1dc7ff7a71ee429ff", size = 1185747, upload-time = "2026-05-15T04:50:27.02Z" }, - { url = "https://files.pythonhosted.org/packages/9d/03/cd0cba295522b91eb55c6b2704f1df895f8226cfe60ab10d4d51d0cc9e69/tiktoken-0.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5d48843bee149630eb735a99e1f4a85b47308d21868ea63163f6e87768d3cfed", size = 1241265, upload-time = "2026-05-15T04:50:28.815Z" }, - { url = "https://files.pythonhosted.org/packages/7e/25/a10efd564402d82c2ff50d12057353ace447aa8007deceaa48641f63d35c/tiktoken-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:fc1c44cd37b43fc46bae593129164f4f281e82ea116b57a85aa81bda57eafc94", size = 876509, upload-time = "2026-05-15T04:50:30.026Z" }, - { url = "https://files.pythonhosted.org/packages/85/8e/144bde4e01df66b34bb865557c7cd754ed08b036217ebd79c9db5e9048a9/tiktoken-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32ac870a806cfb260a02d0cb70426aef02e038297f8ad50df5040bb5af360791", size = 1034888, upload-time = "2026-05-15T04:50:31.579Z" }, - { url = "https://files.pythonhosted.org/packages/36/18/d4ac9d20956cdebca04841316660ed584c2fecdc2b81722a28bc7ad3b1e4/tiktoken-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d9980f11429ed2d737c463bb1fb78cf330caa026adf002f714aced7849a687b", size = 982970, upload-time = "2026-05-15T04:50:32.961Z" }, - { url = "https://files.pythonhosted.org/packages/74/ed/6bb8d05b9f731f749fee5c6f5ca63e981143c826a5985877330507bd13b7/tiktoken-0.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3f277ebea5edd7b8bf03c6f9431e1d67d517530115572b2dc1d465326e8f88c7", size = 1115741, upload-time = "2026-05-15T04:50:34.475Z" }, - { url = "https://files.pythonhosted.org/packages/34/de/2ca96b07a82d972b74fe4b46de055b79c904e45c7eab699354a0bfa697dc/tiktoken-0.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:a116178fa7e1b4065bff05214360373a65cac22f965be7b3f73d00a0dbfe7649", size = 1136523, upload-time = "2026-05-15T04:50:35.782Z" }, - { url = "https://files.pythonhosted.org/packages/ee/dc/9dafec002c2d4424378563cf4cf5c7fb93631d2a55013c8b87554ee4012c/tiktoken-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c397ddda233208345b01bd30f2fca79ff730e55731d0108a603f9bc57f6af3b", size = 1181954, upload-time = "2026-05-15T04:50:36.99Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d0/1f8578c45b2f24759b46f0b50d31878c63c73e6bf0f2227e10ec5c5408dc/tiktoken-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95097e4f89b06403976e498abf61a0ee73a7497e73fb599cb211d8197a054d91", size = 1240069, upload-time = "2026-05-15T04:50:38.221Z" }, - { url = "https://files.pythonhosted.org/packages/aa/90/28d7f154888610aa9237e541986beb62b479df29d193a5a0617dbb1514d0/tiktoken-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:8f2d16e7a7c783ad81f36e457d046d1f1c8af70b22aec8a13238efe531977c41", size = 874748, upload-time = "2026-05-15T04:50:39.587Z" }, - { url = "https://files.pythonhosted.org/packages/9c/83/b096c859c2a47c11731bf2f5885f4028b809dfe2396582883eed9cae372f/tiktoken-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5df5d1507bd245f1ccad4a074698240021239e455eb0bb4ced4e3d7181872154", size = 1034228, upload-time = "2026-05-15T04:50:40.988Z" }, - { url = "https://files.pythonhosted.org/packages/53/61/c68e123b6d753e3fc2751e9b18e732c9d8bf1e1926762e736eee935d931c/tiktoken-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fe806a50664e83a6ffd56cbd1e4f5dcc6cd32a3e7538f70dc38b1a271384545", size = 982978, upload-time = "2026-05-15T04:50:42.195Z" }, - { url = "https://files.pythonhosted.org/packages/ef/8b/96cc178cc584e65d363134500f297790b06cd48cdeb1e8fcf7bbe60f4715/tiktoken-0.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:125bc05005e747f993a83dc67934249932d6e4209854452cd4c0b1d53fba3ba2", size = 1116355, upload-time = "2026-05-15T04:50:43.564Z" }, - { url = "https://files.pythonhosted.org/packages/86/f5/bab735d2c72ea55404b295d02d092644eb5f7cc6205e34d35eb9abfb9ab2/tiktoken-0.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5e6358911cab4adee6712da27d65573496a4f68cf8a2b5fca6a4ad10fc5748cf", size = 1135772, upload-time = "2026-05-15T04:50:44.782Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b9/6de04ebdf904edfaad87788011b3735087a0c9ea671b9027e1e4e965e8c8/tiktoken-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:975cbd78d085d75d26b59660e262736dcaed1e35f8f142cd6291025c01d25486", size = 1182415, upload-time = "2026-05-15T04:50:46.422Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9c/470a05f3b1caf038f44880e334d47ab674e0c80d514c66b375d14d5afa10/tiktoken-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ab9bc99fa020a4c283424590ecd7f3afd70c1c281cb3fa3192a6c3af9f9615", size = 1239879, upload-time = "2026-05-15T04:50:48.052Z" }, - { url = "https://files.pythonhosted.org/packages/42/a6/c1936d16055436cb32e6c6128d68629622e00f4768562f55653752d34768/tiktoken-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:6b1615f0ff71953d19729ceb18865429c185b0a23c5353f1bbca34a394bf60f7", size = 874829, upload-time = "2026-05-15T04:50:49.202Z" }, - { url = "https://files.pythonhosted.org/packages/d6/07/acb5992c3772b5a36284f742cfb7a5895aa4471d1848ac31464ad50d7fdf/tiktoken-0.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6eb4a5bfbc6426938026b1a334e898ac53541360d62d8c689870160cc80abd67", size = 1033600, upload-time = "2026-05-15T04:50:50.4Z" }, - { url = "https://files.pythonhosted.org/packages/14/e9/742e9aec30f59b9f161f7ff7cd072e02ea836c9e1c0854a8076dfcd40d5c/tiktoken-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:43cee3e5400573b2046fbf092cc7a5bc30164f9e4c95ce20714da929df48737a", size = 982516, upload-time = "2026-05-15T04:50:52.03Z" }, - { url = "https://files.pythonhosted.org/packages/72/74/ca1541b053e7648254d2e4b42a253e1bb4359f2c91a0a8d49228c794e1a0/tiktoken-0.13.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:7de52e3f566d19b3b11bd37eea552c6c305ad74081f736882bd44d148ed4c48d", size = 1115518, upload-time = "2026-05-15T04:50:53.543Z" }, - { url = "https://files.pythonhosted.org/packages/46/e3/93825eaf5a4a504795b787e5d5dea07fbeb3dabf97aa7b450be8bde59c89/tiktoken-0.13.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:51384448aa508e4df84c0f7c1dc3211c7f7b8096325660ee5fc82f3e11b381ce", size = 1136867, upload-time = "2026-05-15T04:50:55.191Z" }, - { url = "https://files.pythonhosted.org/packages/8c/46/002b68de6827091d5ae90b048f326e8aad8d953520950e5ce1508879414f/tiktoken-0.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e28157350f7ebf35008dd8e9e0fdb621f976e4230c881099c85e8cf07eaa50e2", size = 1181826, upload-time = "2026-05-15T04:50:56.296Z" }, - { url = "https://files.pythonhosted.org/packages/db/c6/d393e3185a276505182f7abd93fe714f3c444a2be9180798fa052347504e/tiktoken-0.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:165cf1820ea4a354985c2490a5205d4cc74661c934aca79dd0368232fff94e0f", size = 1239489, upload-time = "2026-05-15T04:50:57.918Z" }, - { url = "https://files.pythonhosted.org/packages/b7/4d/bc07d1f1635d4897a202acc0ae11c2886eaa7325c359ba4741b47bf8e225/tiktoken-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6c43a675ca14f6f2749ba7f12075d37456015a24b859f2517b9beb4ef30807ec", size = 873820, upload-time = "2026-05-15T04:50:59.528Z" }, - { url = "https://files.pythonhosted.org/packages/8c/93/0dd6adca026a616c3a92974566b43381eea4b475ce1f36c062b8271a9ac5/tiktoken-0.13.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaaaef47c2406277181d2086484c317bf7fc433e2d5d03ff94f56b0dcec87471", size = 1034977, upload-time = "2026-05-15T04:51:00.957Z" }, - { url = "https://files.pythonhosted.org/packages/d9/77/5ec6e6bc5b30bed6d93f7f2162d8f6b32437b3ba27cb527cfe004f6109c9/tiktoken-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ca8b310bd93b3772cb1b7922d915446864860f562bdfe4825c63a0aed3fb28cd", size = 983635, upload-time = "2026-05-15T04:51:02.629Z" }, - { url = "https://files.pythonhosted.org/packages/94/b0/c8ae9aff00d625c50659b4513e707a0462c4bf5d4d6cc1b802103225c02e/tiktoken-0.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:32e0c12305105002c047b3bb1070b0dd9a73b0cb3b2856a8972b810e7a4f5881", size = 1116036, upload-time = "2026-05-15T04:51:04.082Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ac/6a5dddd1d0a6018ecb389bd0353e6b4a515eb4d2286611bd0ace1937b9e1/tiktoken-0.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:5ba5fd62507a932d1241346179e3b39bc7bf7408f03c272652d93b3bedf5db24", size = 1135544, upload-time = "2026-05-15T04:51:05.229Z" }, - { url = "https://files.pythonhosted.org/packages/f4/b8/585032b4384b2f7dcdaddcb52865c83a701a420d09e3c2b4a2be1c450c57/tiktoken-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d108bc2d470fc53c8ecd24f2c0fd2b5f98c33e87cdb6aa2e9b8c5dced703d273", size = 1182217, upload-time = "2026-05-15T04:51:06.517Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b6/993ff1ded3958215fd341a847b8e5ffeb5de473f435296870d314fc91ac4/tiktoken-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb99cb5127449f58d0a2d5f5ccfb390d8dbdfd919c221246caaee29d8725ed51", size = 1239404, upload-time = "2026-05-15T04:51:07.843Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3d/fef7e06e3b33e7538db0ced734cf9fe23b6832d2ac4990c119c377aec55e/tiktoken-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:115c4f26ffa11caac8b54eea35c2ad38c612c20a48d35dd15d70a02ac6f51f58", size = 918686, upload-time = "2026-05-15T04:51:08.925Z" }, - { url = "https://files.pythonhosted.org/packages/c1/82/a7fc44582bc32ab00de988a2299bf77c077f59068b233109e34b7d6ca7e6/tiktoken-0.13.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:472527e9132952f2fbf77cd290658bacf003d4d5a3fabc18e5fbd407cbae4d9b", size = 1034454, upload-time = "2026-05-15T04:51:10.035Z" }, - { url = "https://files.pythonhosted.org/packages/37/d0/24d8a890c14f432a05cea669c17bebeaa99f96a7c79523b590f564246411/tiktoken-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e2f67d27c9626cdd25fe33d9313c5cdb3d8d82da646b68d6eb8e7e9c20e6448", size = 982976, upload-time = "2026-05-15T04:51:11.23Z" }, - { url = "https://files.pythonhosted.org/packages/49/b7/2ab43f62788a9266187a9bfc1d3af99ad83e5eaa25fbef168a69cd5ad14f/tiktoken-0.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2b920b35805cd64585a37c3dc7ce65fba4d2d36016be01e1d7942482ca29093a", size = 1115526, upload-time = "2026-05-15T04:51:12.608Z" }, - { url = "https://files.pythonhosted.org/packages/64/39/1494321ed323ce7a14d88e3cd6cb9058625977df1c6961ddc492bd10a9f3/tiktoken-0.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:493af3aa28a4aaf2e3d2600a2ee717252c9bf5ab38fff94eb5a02db5ab77e5ad", size = 1136466, upload-time = "2026-05-15T04:51:13.926Z" }, - { url = "https://files.pythonhosted.org/packages/96/d9/dfd086aa2d918c563a140720e0ce296cada1634efd2783d5cf51e05f984e/tiktoken-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6644c9c2b5cf3916f5a3641d7d12fdb3f006a7b3d9ff6acdaec44e29ab1ff91e", size = 1181863, upload-time = "2026-05-15T04:51:15.025Z" }, - { url = "https://files.pythonhosted.org/packages/2f/68/a18b4f307086954fdae32714cb4f85562e34f9d34ab206e61f1816aa6018/tiktoken-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cb65b60b9408563676d874a3a4ee573370066f0dc4e29d84e82e989c6517424", size = 1239218, upload-time = "2026-05-15T04:51:16.103Z" }, - { url = "https://files.pythonhosted.org/packages/16/5b/f2aa703a4fc5d2dff73460a7d46cc2f3f44aa0f3dd8eeb20d2a0ecf68862/tiktoken-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:85b78cc3a2c3d48723ca751fa981f1fedccd54194ca0471b957364353a898b07", size = 918110, upload-time = "2026-05-15T04:51:17.237Z" }, -] - -[[package]] -name = "tokenizers" -version = "0.23.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c1/60/21f715d9faba5f5407ff759472ade058ec4a507ad62bcea47cb847239a73/tokenizers-0.23.1.tar.gz", hash = "sha256:1feeeadf865a7915adc25445dea30e9933e593c31bb96c277cee36de227c8bfa", size = 365748, upload-time = "2026-04-27T14:43:25.606Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/39/b87a87d5bb9470610b80a2d31df42fcffeaf35118b8b97952b2aff598cc7/tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e03d6ffcbe0d56ee9c1ccd070e70a13fa750727c0277e138152acbc0252c2224", size = 3146732, upload-time = "2026-04-27T14:43:15.427Z" }, - { url = "https://files.pythonhosted.org/packages/e2/6a/068ed9f6e444c9d7e9d55ce134181325700f3d7f30410721bdc8f848d727/tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0948bbb1ac1d7cdfc9fb6d62c596e3b7550036ad60ecd654a66ad273326324e", size = 3054954, upload-time = "2026-04-27T14:43:13.745Z" }, - { url = "https://files.pythonhosted.org/packages/6c/36/e006edf031154cba92b8416057d92c3abe3635e4c4b0aa0b5b9bb39dde70/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf13402aff9bc533c89cb849ec3b412dc3fbeacc9744840e423d7bf3f7dc0e3", size = 3374081, upload-time = "2026-04-27T14:43:01.241Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ef/7735d226f9c7f874a6bee5e3f27fb25ecabdf207d37b8cf45286d0795893/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f836ca703b89ae07919a309f9651f7a88fd5a33d5f718ba5ad0870ec0256bad6", size = 3247641, upload-time = "2026-04-27T14:43:03.856Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d9/24827036f6e21297bfffda0768e58eb6096a4f411e932964a01707857931/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae848657742035523fdf261773630cb819a26995fcd3d9ecae0c1daf6e5a4959", size = 3585624, upload-time = "2026-04-27T14:43:10.664Z" }, - { url = "https://files.pythonhosted.org/packages/0c/9a/22f3582b3a4f49358293a5206e25317621ee4526bfe9cdaa0f07a12e770e/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b09e85775d5187941e7bab30e941b4134ab4a7dd8c68e783d231fb7ca27c51", size = 3844062, upload-time = "2026-04-27T14:43:05.643Z" }, - { url = "https://files.pythonhosted.org/packages/7e/65/b8f8814eef95800f20721384136d9a1d22241d50b2874357cb70542c392f/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea5a0ce170074329faaa8ea3f6400ecde604b6678192688533af80980daae71a", size = 3460098, upload-time = "2026-04-27T14:43:08.854Z" }, - { url = "https://files.pythonhosted.org/packages/0d/d5/1353e5f677ec27c2494fb6a6725e82d56c985f53e90ec511369e7e4f02c6/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b405006415ea148a992d093699c66eb01952bf59f4d5727089a98bda45a4", size = 3346235, upload-time = "2026-04-27T14:43:12.377Z" }, - { url = "https://files.pythonhosted.org/packages/71/89/39b6b8fc073fb6d413d0147aa333dc7eff7be65639ac9d19930a0b21bf33/tokenizers-0.23.1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:56f3a77de629917652f876294dc9fe6bad4a0c43bc229dc72e59bb23a0f4729a", size = 3426398, upload-time = "2026-04-27T14:43:07.264Z" }, - { url = "https://files.pythonhosted.org/packages/0f/80/127c854da64827e5b79264ce524993a90dddcb320e5cd42412c5c02f9e8a/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d10a6d957ef01896dc274e890eee27d41bd0e74ef31e60616f0fc311345184e", size = 9823279, upload-time = "2026-04-27T14:43:17.222Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ba/44c2502feb1a058f096ddfb4e0996ef3225a01a388e1a9b094e91689fe93/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1974288a609c343774f1b897c8b482c791ab17b75ab5c8c2b1737565c1d82288", size = 9644986, upload-time = "2026-04-27T14:43:19.45Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c1/464019a9fb059870bfe4eebb4ba12208f3042035e258bf5e782906bd3847/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:120468fb4c24faf0543c835a4fabafa4deb3f20a035c9b6e83d0b553a97615d4", size = 9976181, upload-time = "2026-04-27T14:43:21.463Z" }, - { url = "https://files.pythonhosted.org/packages/79/94/3ac1432bda31626071e9b6a12709b97ae05131c804b94c8f3ac622c5da32/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e3d8f40ea6268047de7046906326abed5134f27d4e8447b23763afe5808c8a96", size = 10113853, upload-time = "2026-04-27T14:43:23.617Z" }, - { url = "https://files.pythonhosted.org/packages/6a/dd/631b21433c771b1382535326f0eca80b9c9cee2e64961dd993bc9ac4669e/tokenizers-0.23.1-cp310-abi3-win32.whl", hash = "sha256:93120a930b919416da7cd10a2f606ac9919cc69cacae7980fa2140e277660948", size = 2536263, upload-time = "2026-04-27T14:43:29.888Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/2553f72aaf65a2797d4229e37fa7fbe38ffbf3e32912d31bdd78b3323e59/tokenizers-0.23.1-cp310-abi3-win_amd64.whl", hash = "sha256:e7bfaf995c1bdbbd21d13539decb6650967013759318627d85daeb7881af16b7", size = 2798223, upload-time = "2026-04-27T14:43:28.51Z" }, - { url = "https://files.pythonhosted.org/packages/cd/2b/2be299bab55fc595e3d38567edb1a87f86e594842968fa9515a07bdcf422/tokenizers-0.23.1-cp310-abi3-win_arm64.whl", hash = "sha256:a26197957d8e4425dfba746315f3c425ea00cfa8367c5fbc4ec73447893dcea9", size = 2664127, upload-time = "2026-04-27T14:43:26.949Z" }, -] - [[package]] name = "tomli" version = "2.4.1" @@ -4762,33 +2558,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/53/4a33dc81da39db7b31e5622333df361e8fe055b7ec636bd5fea762c9182d/tox_uv_bare-1.35.2-py3-none-any.whl", hash = "sha256:c0d590a41d1054a1ad0874e9e5943ff52402786e3d4599d8f8d37a65b566ef53", size = 22307, upload-time = "2026-05-05T01:34:17.681Z" }, ] -[[package]] -name = "tqdm" -version = "4.68.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/87/d7/0535a28b1f5f24f6612fb3ff1e89fb1a8d160fee0f976e0aa6803862134b/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", size = 170596, upload-time = "2026-06-17T07:36:52.105Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" }, -] - -[[package]] -name = "typer" -version = "0.25.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-doc" }, - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, -] - [[package]] name = "types-pyyaml" version = "6.0.12.20260518" @@ -4966,55 +2735,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, ] -[package.optional-dependencies] -standard = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "httptools" }, - { name = "python-dotenv" }, - { name = "pyyaml" }, - { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, - { name = "watchfiles" }, - { name = "websockets" }, -] - -[[package]] -name = "uvloop" -version = "0.22.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, - { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, - { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, - { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, - { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, - { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, - { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, - { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, - { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, - { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, - { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, - { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, - { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, - { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, - { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, - { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, - { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, - { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, - { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, - { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, - { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, - { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, - { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, - { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, - { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, - { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, -] - [[package]] name = "virtualenv" version = "21.5.1" @@ -5057,119 +2777,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, ] -[[package]] -name = "watchfiles" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/3d/8024c801df84d1587740d0359e7fdd80afeae3d159011f3d5376dd82f18e/watchfiles-1.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201", size = 400242, upload-time = "2026-05-18T04:31:19.014Z" }, - { url = "https://files.pythonhosted.org/packages/87/5b/f4dfd45323e949984a3a7f9dc31d1cbb049921e7d98253488dda72ccdaa9/watchfiles-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5", size = 394562, upload-time = "2026-05-18T04:30:08.46Z" }, - { url = "https://files.pythonhosted.org/packages/98/d8/19483ef075d601c409bce8bcbb5c0f81a10876fff870400568f08ce484a1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a", size = 456611, upload-time = "2026-05-18T04:30:45.723Z" }, - { url = "https://files.pythonhosted.org/packages/b1/6a/cc81fbe7ee42f2f22e661a6e12def7807e01b14b2f39e0ff83fd373fd307/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1", size = 461379, upload-time = "2026-05-18T04:31:29.292Z" }, - { url = "https://files.pythonhosted.org/packages/b1/57/7e669002082c0a0f4fb5113bb70125f7110124b846b0a11bc5ae8e90eac1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717", size = 493556, upload-time = "2026-05-18T04:30:05.44Z" }, - { url = "https://files.pythonhosted.org/packages/45/7d/f60a2b19807b21fe8281f3a8da4f59eef0d5f96825ac4680ba2d4f2ebf91/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b", size = 575255, upload-time = "2026-05-18T04:30:40.568Z" }, - { url = "https://files.pythonhosted.org/packages/bd/49/77f5b5e6efbcd57482f74948ebb1b97e5c0046d6b61475042d830c84b3ff/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5", size = 467052, upload-time = "2026-05-18T04:31:17.942Z" }, - { url = "https://files.pythonhosted.org/packages/ee/5a/73e2959af1b97fd5d556f9a8bdba017be23ceeef731869d5eaa0a753d5a3/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e", size = 456858, upload-time = "2026-05-18T04:30:30.182Z" }, - { url = "https://files.pythonhosted.org/packages/50/57/1bc8c27fad7e6c19bddee15d276dbb6ab72480ec01c127afff1673aee417/watchfiles-1.2.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165", size = 467579, upload-time = "2026-05-18T04:32:15.897Z" }, - { url = "https://files.pythonhosted.org/packages/09/6c/3c2e44edba3553c5e3c3b8c8a2a6dee6b9e12ae2cf4bd2378bebf9dc3038/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6", size = 633253, upload-time = "2026-05-18T04:31:37.123Z" }, - { url = "https://files.pythonhosted.org/packages/30/c2/d8c84a882ab39bbefcc4915ab3e91830b7a7e990c5570b0b69075aba3faf/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5", size = 660713, upload-time = "2026-05-18T04:31:24.62Z" }, - { url = "https://files.pythonhosted.org/packages/a9/07/f97736a5fc605364fe67b25e9fa4a6965dfd4840d50c406ada507e9d735f/watchfiles-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8", size = 277222, upload-time = "2026-05-18T04:31:21.131Z" }, - { url = "https://files.pythonhosted.org/packages/cf/99/2b04981977fc2608afd60360d928c6aecf6b950292ca221d98f4005f6694/watchfiles-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22", size = 290274, upload-time = "2026-05-18T04:31:45.966Z" }, - { url = "https://files.pythonhosted.org/packages/3c/74/f7f58a7075ee9cf612b0cfcddb78b8cd8234f0742d6f0075cf0da2dde1c6/watchfiles-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7", size = 283460, upload-time = "2026-05-18T04:31:39.126Z" }, - { url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" }, - { url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" }, - { url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" }, - { url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" }, - { url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" }, - { url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" }, - { url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" }, - { url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" }, - { url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" }, - { url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" }, - { url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" }, - { url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" }, - { url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" }, - { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" }, - { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" }, - { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" }, - { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" }, - { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" }, - { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" }, - { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" }, - { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" }, - { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" }, - { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" }, - { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" }, - { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" }, - { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" }, - { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" }, - { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" }, - { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" }, - { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" }, - { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" }, - { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" }, - { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" }, - { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" }, - { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, - { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, - { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, - { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, - { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, - { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, - { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, - { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, - { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, - { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, - { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, - { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, - { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, - { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, - { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, - { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, - { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, - { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, - { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, - { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, - { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, - { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, - { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, - { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, - { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, - { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, - { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, - { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, - { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, - { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, - { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, - { url = "https://files.pythonhosted.org/packages/23/f4/7513ef1e85fc4c6331b59479d6d72661fc391fbe543678052ac72c8b6c19/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2", size = 403050, upload-time = "2026-05-18T04:30:36.753Z" }, - { url = "https://files.pythonhosted.org/packages/27/0b/a54103cfd732bb703c7a749222011a0483ef3705948dae3b203158601119/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db", size = 396629, upload-time = "2026-05-18T04:32:03.268Z" }, - { url = "https://files.pythonhosted.org/packages/5e/2c/73f31a3b893886206c3f54d73e8ad8dee58cdb2f69ad2622e0a8a9e07f4e/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7", size = 457318, upload-time = "2026-05-18T04:31:01.932Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f9/45d021e4a5cc7b9dd567f7cbb06d3b75f751a690063fb6cc7ec60f4e46b7/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0", size = 457771, upload-time = "2026-05-18T04:30:56.331Z" }, -] - -[[package]] -name = "websocket-client" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, -] - [[package]] name = "websockets" version = "15.0.1" @@ -5425,118 +3032,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/75/3f/9036bc57048a72707bf97bc7632335ad32f6047301035de46d7cabd14e5c/xxhash-3.7.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:da6f19cb2a75fe8417ca03b40c7efdcfeaa0d24b9cae782199498b2cb0a29911", size = 31935, upload-time = "2026-06-24T08:21:22.657Z" }, ] -[[package]] -name = "yarl" -version = "1.24.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/c5/1ce244152ff2839645e7cae92f90e7bafcb2c52bea7ff586ac714f14f5df/yarl-1.24.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:36348bebb147b83818b9d7e673ea4debc75970afc6ffdc7e3975ad05ce5a58c1", size = 128971, upload-time = "2026-05-19T21:28:20.543Z" }, - { url = "https://files.pythonhosted.org/packages/87/5a/00f36967203ed89cb3acd2c8ed526cc3fed9418eb70ce128160a911c8499/yarl-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a97e42c8a2233f2f279ecadd9e4a037bcb5d813b78435e8eedd4db5a9e9708c", size = 91507, upload-time = "2026-05-19T21:28:22.556Z" }, - { url = "https://files.pythonhosted.org/packages/31/d0/1fb0c1cd27288f39f6974da4318c32768d72c9890984541fdf1e2e32a51d/yarl-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d027d56f1035e339d1001ac33eceab5b2ec8e42e449787bb75e289fb9a5cd1d", size = 91343, upload-time = "2026-05-19T21:28:24.092Z" }, - { url = "https://files.pythonhosted.org/packages/03/ce/d4a646508bed2f8dec6435b40166fe9308dd191262033d3f307b2bbcaecd/yarl-1.24.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a6377060e7927187a42b7eb202090cbe2b34933a4eeaf90e3bd9e33432e5cae", size = 105704, upload-time = "2026-05-19T21:28:25.872Z" }, - { url = "https://files.pythonhosted.org/packages/4b/07/b3278e82d8bc41485bcf6d856cd0433262593de615b1d3dc43bd3f5bead4/yarl-1.24.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:17076578bce0049a5ce57d14ad1bded391b68a3b213e9b81b0097b090244999a", size = 97281, upload-time = "2026-05-19T21:28:27.352Z" }, - { url = "https://files.pythonhosted.org/packages/17/5b/4cee6e7c92e487bebe7afc797da0aa54a248ab4e776a68fe369ec29665a5/yarl-1.24.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:50713f1d4d6be6375bb178bb43d140ee1acb8abe589cd723320b7925a275be1e", size = 114020, upload-time = "2026-05-19T21:28:29.458Z" }, - { url = "https://files.pythonhosted.org/packages/5c/82/111076571545a7d4f9cca3fbd5c6f40615af58642be09f12328f48022468/yarl-1.24.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:34263e2fa8fb5bb63a0d97706cda38edbad62fddb58c7f12d6acbc092812aa50", size = 111450, upload-time = "2026-05-19T21:28:31.262Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ec/08f671f69a444d704aeecebf92af659b67b97a869942411d0a578b08c334/yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49016d82f032b1bd1e10b01078a7d29ae71bf468eeae0ea22df8bab691e60003", size = 106384, upload-time = "2026-05-19T21:28:32.856Z" }, - { url = "https://files.pythonhosted.org/packages/e5/86/ce41e7a7a199340b2330d52b60f25c4074b6636dd0e60b1a80d31a9db042/yarl-1.24.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3f6d2c216318f8f32038ca3f72501ba08536f0fd18a36e858836b121b2deed9f", size = 106153, upload-time = "2026-05-19T21:28:35.222Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5d/31be8a729531ab3e55ac3e7e5c800be8c89ea98947f418b2f6ea259fb6ee/yarl-1.24.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:08d3a33218e0c64393e7610284e770409a9c31c429b078bcb24096ed0a783b8f", size = 105322, upload-time = "2026-05-19T21:28:36.642Z" }, - { url = "https://files.pythonhosted.org/packages/47/9b/b57afb22b386ae87ac9940f09878b98d8c333f89113e6fc96fcf4ca9eb64/yarl-1.24.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5d699376c4ca3cba49bbfae3a05b5b70ded572937171ce1e0b8d87118e2ba294", size = 99057, upload-time = "2026-05-19T21:28:38.386Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4f/06348c27c8389256c313e8a57d796808fc0264c915dd5e7cfd3c0e314dc7/yarl-1.24.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a1cab588b4fa14bea2e55ebea27478adfb05372f47573738e1acc4a36c0b05d2", size = 113502, upload-time = "2026-05-19T21:28:40.091Z" }, - { url = "https://files.pythonhosted.org/packages/5f/1c/284f307b298e4a17b7943b07d9d7ecc4151537f8d137ba51f3bb6c31ca20/yarl-1.24.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ec87ccc31bd21db7ad009d8572c127c1000f268517618a4cc09adba3c2a7f21c", size = 105253, upload-time = "2026-05-19T21:28:41.987Z" }, - { url = "https://files.pythonhosted.org/packages/c8/bf/0de123bec8619e45c80cbded9085f61b5b4a9eddb8abe6d25d28ee1ec866/yarl-1.24.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d1dd47a22843b212baa8d74f37796815d43bd046b42a0f41e9da433386c3136b", size = 111345, upload-time = "2026-05-19T21:28:43.93Z" }, - { url = "https://files.pythonhosted.org/packages/90/af/0248eb065e51129d2a9b2436cd1b5c772c19a6b04e5b6a186955671e3319/yarl-1.24.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7b54b9c67c2b06bd7b9a77253d242124b9c95d2c02def5a1144001ee547dd9d5", size = 106558, upload-time = "2026-05-19T21:28:45.806Z" }, - { url = "https://files.pythonhosted.org/packages/21/3c/f960d7a65ef97d8ba9b424fb5128796a4bc710fc6df2ddbbd7dfdc3bbd20/yarl-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:f8fdbcff8b2c7c9284e60c196f693588598ddcee31e11c18e14949ce44519d45", size = 92808, upload-time = "2026-05-19T21:28:48.465Z" }, - { url = "https://files.pythonhosted.org/packages/03/1a/49fb03750e4de4d2284cd5b885a383133c34eef45bd59631b2bb8b7e81e8/yarl-1.24.2-cp311-cp311-win_arm64.whl", hash = "sha256:b32c37a7a337e90822c45797bf3d79d60875cfcccd3ecc80e9f453d87026c122", size = 87610, upload-time = "2026-05-19T21:28:50.07Z" }, - { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" }, - { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" }, - { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" }, - { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" }, - { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" }, - { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" }, - { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" }, - { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" }, - { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" }, - { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" }, - { url = "https://files.pythonhosted.org/packages/a7/25/722e3b93bd687009afb2d59a35e13d30ddd8f80571445bb0c4e4ce26ec66/yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad", size = 92901, upload-time = "2026-05-19T21:29:20.014Z" }, - { url = "https://files.pythonhosted.org/packages/39/47/4486ccfb674c04854a1ef8aa77868b6a6f765feaf69633409d7ca4f02cb8/yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30", size = 87229, upload-time = "2026-05-19T21:29:22.1Z" }, - { url = "https://files.pythonhosted.org/packages/82/62/fcf0ce677f17e5c471c06311dd25964be38a4c586993632910d2e75278bc/yarl-1.24.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491ac9141decf49ee8030199e1ee251cdff0e131f25678817ff6aa5f837a3536", size = 128978, upload-time = "2026-05-19T21:29:23.83Z" }, - { url = "https://files.pythonhosted.org/packages/d3/58/8e63299bb71ed61a834121d9d3fe6c9fcf2a6a5d09754ff4f20f2d20baf5/yarl-1.24.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e89418f65eda18f99030386305bd44d7d504e328a7945db1ead514fbe03a0607", size = 91733, upload-time = "2026-05-19T21:29:25.375Z" }, - { url = "https://files.pythonhosted.org/packages/c1/24/16748d5dab6daec8b0ed81ccec639a1cded0f18dcc62a4f696b4fe366c37/yarl-1.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdfcce633b4a4bb8281913c57fcafd4b5933fbc19111a5e3930bbd299d6102f1", size = 91113, upload-time = "2026-05-19T21:29:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/1b/66/b63fff7b71211e866624b21432d5943cbb633eb0c2872d9ee3070648f22c/yarl-1.24.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863297ddede92ee49024e9a9b11ecb59f310ca85b60d8537f56bed9bbb5b1986", size = 103899, upload-time = "2026-05-19T21:29:28.842Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ac/ba1974b8533909636f7733fe86cf677e3619527c3c2fa913e0ea89c48757/yarl-1.24.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:374423f70754a2c96942ede36a29d37dc6b0cb8f92f8d009ddf3ed78d3da5488", size = 97862, upload-time = "2026-05-19T21:29:31.086Z" }, - { url = "https://files.pythonhosted.org/packages/1b/a5/123ac993b5c2ba6f554a140305620cb8f150fa543711bbc49be3ec0a65a4/yarl-1.24.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33a29b5d00ccbf3219bb3e351d7875739c19481e030779f48cc46a7a71681a9b", size = 111060, upload-time = "2026-05-19T21:29:32.657Z" }, - { url = "https://files.pythonhosted.org/packages/23/37/c472d3af3509688392134a88a825276770a187f1daa4de3f6dc0a327a751/yarl-1.24.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9532c57211730c515341af11fef6e9b61d157487272a096d0c04da445642592", size = 110613, upload-time = "2026-05-19T21:29:34.379Z" }, - { url = "https://files.pythonhosted.org/packages/df/88/09c28dad91e662ccfaa1b78f1c57badde74fc9d0b23e74aef644750ecd73/yarl-1.24.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91e72cf093fd833483a97ee648e0c053c7c629f51ff4a0e7edd84f806b0c5617", size = 107012, upload-time = "2026-05-19T21:29:36.216Z" }, - { url = "https://files.pythonhosted.org/packages/07/ab/9d4f69d571a94f4d112fa7e2e007200f5a54d319f58c82ac7b7baa61f5c6/yarl-1.24.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3177bc0a768ef3bacceb4f272632990b7bea352f1b2f1eee9d6d6ff16516f92", size = 105887, upload-time = "2026-05-19T21:29:38.746Z" }, - { url = "https://files.pythonhosted.org/packages/8e/9a/000b2b66c0d772a499fc531d21dab92dfeb73b640a12eed6ba89f49bb2d0/yarl-1.24.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e196952aacaf3b232e265ff02980b64d483dc0972bd49bcb061171ff22ac203a", size = 103620, upload-time = "2026-05-19T21:29:40.368Z" }, - { url = "https://files.pythonhosted.org/packages/41/7c/7c1050f73450fbdaa3f0c72017059f00ce5e13366692f3dba25275a1083d/yarl-1.24.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:204e7a61ce99919c0de1bf904ab5d7aa188a129ea8f690a8f76cfb6e2844dc44", size = 100599, upload-time = "2026-05-19T21:29:42.66Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b1/29e5756b3926705f5f6089bd5b9f50a56eaac550da6e260bf713ead44d04/yarl-1.24.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b156914620f0b9d78dc1adb3751141daee561cfec796088abb89ed49d220f1a", size = 110604, upload-time = "2026-05-19T21:29:44.632Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4b/8415bc96e9b150cde942fbac9a8182985e58f40ce5c54c34ed015407d3ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8372a2b976cf70654b2be6619ab6068acabb35f724c0fda7b277fbf53d66a5cf", size = 105161, upload-time = "2026-05-19T21:29:46.755Z" }, - { url = "https://files.pythonhosted.org/packages/8b/d4/cde059abfa229553b7298a2eadde2752e723d50aeedaef86ce59da2718ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f9a1e9b622ca284143aab5d885848686dcd85453bb1ca9abcdb7503e64dc0056", size = 110619, upload-time = "2026-05-19T21:29:48.972Z" }, - { url = "https://files.pythonhosted.org/packages/e7/2c/d6a6c9a61549f7b6c7e6dc6937d195bcf069582b47b7200dcd0e7b256acf/yarl-1.24.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:810e19b685c8c3c5862f6a38160a1f4e4c0916c9390024ec347b6157a45a0992", size = 107362, upload-time = "2026-05-19T21:29:51Z" }, - { url = "https://files.pythonhosted.org/packages/92/dd/3ae5fe417e9d1c353a548553326eb9935e76b6b727161563b424cc296df3/yarl-1.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:7d37fb7c38f2b6edab0f845c4f85148d4c44204f52bc127021bd2bc9fdbf1656", size = 92667, upload-time = "2026-05-19T21:29:52.743Z" }, - { url = "https://files.pythonhosted.org/packages/10/cc/a7beb239f78f27fca1b053c8e8595e4179c02e62249b4687ec218c370c50/yarl-1.24.2-cp313-cp313-win_arm64.whl", hash = "sha256:1e831894be7c2954240e49791fa4b50c05a0dc881de2552cfe3ffd8631c7f461", size = 87069, upload-time = "2026-05-19T21:29:54.442Z" }, - { url = "https://files.pythonhosted.org/packages/40/0e/e08087695fc12789263821c5dc0f8dc52b5b17efd0887cacf419f8a43ba3/yarl-1.24.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2", size = 129670, upload-time = "2026-05-19T21:29:56.631Z" }, - { url = "https://files.pythonhosted.org/packages/3a/98/ab4b5ed1b1b5cd973c8a3eb994c3a6aefb6ce6d399e21bb5f0316c33815c/yarl-1.24.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630", size = 91916, upload-time = "2026-05-19T21:29:58.645Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8", size = 91625, upload-time = "2026-05-19T21:30:00.412Z" }, - { url = "https://files.pythonhosted.org/packages/02/a7/45baabfff76829264e623b185cff0c340d7e11bf3e1cd9ea37e7d17934bd/yarl-1.24.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14", size = 104574, upload-time = "2026-05-19T21:30:02.544Z" }, - { url = "https://files.pythonhosted.org/packages/f3/40/3a5ab144d3d650ca37d4f4b57e56169be8af3ca34c448793e064b30baaed/yarl-1.24.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535", size = 97534, upload-time = "2026-05-19T21:30:04.319Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b5/5658fef3681fb5776b4513b052bec750009f47b3a592251c705d75375798/yarl-1.24.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14", size = 111481, upload-time = "2026-05-19T21:30:05.988Z" }, - { url = "https://files.pythonhosted.org/packages/4c/06/fdcd7dde037f00866dce123ed4ba23dba94beb56fc4cf561668d27be37f2/yarl-1.24.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3", size = 111529, upload-time = "2026-05-19T21:30:07.738Z" }, - { url = "https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208", size = 107338, upload-time = "2026-05-19T21:30:09.713Z" }, - { url = "https://files.pythonhosted.org/packages/ae/04/23049463f729bd899df203a7960505a75333edd499cda8aa1d5a82b64df5/yarl-1.24.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50", size = 106147, upload-time = "2026-05-19T21:30:11.365Z" }, - { url = "https://files.pythonhosted.org/packages/14/18/04a4b5830b43ed5e4c5015b40e9f6241ad91487d71611061b4e111d6ac80/yarl-1.24.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd", size = 104272, upload-time = "2026-05-19T21:30:12.978Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f7/8cffdf319aee7a7c1dbd07b61d91c3e3fda460c7a93b5f93e445f3806c4c/yarl-1.24.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67", size = 99962, upload-time = "2026-05-19T21:30:15.001Z" }, - { url = "https://files.pythonhosted.org/packages/d7/39/b3cce3b7dbef64ac700ad4cea156a207d01bede0f507587616c364b5468e/yarl-1.24.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1", size = 111063, upload-time = "2026-05-19T21:30:16.683Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ea/100818505e7ebf165c7242ff17fdf7d9fee79e27234aeca871c1082920d7/yarl-1.24.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1", size = 105438, upload-time = "2026-05-19T21:30:18.769Z" }, - { url = "https://files.pythonhosted.org/packages/8f/d2/e075a0b32aa6625087de9e653087df0759fed5de4a435fef594181102a77/yarl-1.24.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b", size = 111458, upload-time = "2026-05-19T21:30:21.024Z" }, - { url = "https://files.pythonhosted.org/packages/e6/5c/ceea7ba98b65c8eb8d947fdc52f9bedfcd43c6a57c9e3c90c17be8f324a3/yarl-1.24.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8", size = 107589, upload-time = "2026-05-19T21:30:23.412Z" }, - { url = "https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl", hash = "sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0", size = 94424, upload-time = "2026-05-19T21:30:25.425Z" }, - { url = "https://files.pythonhosted.org/packages/92/10/7dc07a0e22806a9280f42a57361395506e800c64e22737cd7b0886feab42/yarl-1.24.2-cp314-cp314-win_arm64.whl", hash = "sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57", size = 88690, upload-time = "2026-05-19T21:30:27.623Z" }, - { url = "https://files.pythonhosted.org/packages/9e/13/d5b8e2c8667db955bcb3de233f18798fefe7edf1d7429c2c9d4f9c401114/yarl-1.24.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b", size = 136248, upload-time = "2026-05-19T21:30:29.297Z" }, - { url = "https://files.pythonhosted.org/packages/de/46/a4a97c05c9c9b8fd266bb2a0df12992c7fbd02391eb9640583411b6dab32/yarl-1.24.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761", size = 95084, upload-time = "2026-05-19T21:30:31.031Z" }, - { url = "https://files.pythonhosted.org/packages/95/b2/845cf2074a015e6fe0d0808cf1a2d9e868386c4220d657ebd8302b199043/yarl-1.24.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8", size = 95272, upload-time = "2026-05-19T21:30:33.062Z" }, - { url = "https://files.pythonhosted.org/packages/fe/16/e69d4aa244aef45235ddfebc0e04036a6829842bc5a6a795aedc6c998d23/yarl-1.24.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed", size = 101497, upload-time = "2026-05-19T21:30:34.842Z" }, - { url = "https://files.pythonhosted.org/packages/15/94/c07107715d621076863ee88b3ddf183fa5e9d4aba5769623c9979828410a/yarl-1.24.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543", size = 94002, upload-time = "2026-05-19T21:30:37.724Z" }, - { url = "https://files.pythonhosted.org/packages/a9/35/fc1bbdd895b5e4010b8fdd037f7ed3aa289d3863e08231b30231ca9a0815/yarl-1.24.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0", size = 106524, upload-time = "2026-05-19T21:30:40.196Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/32b66d0a4ba47c296cf86d03e2c67bff58399fe6d6d84d5205c04c66cc6d/yarl-1.24.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024", size = 106165, upload-time = "2026-05-19T21:30:41.888Z" }, - { url = "https://files.pythonhosted.org/packages/95/47/37cb5ff50c5e825d4d38e81bb04d1b7e96bf960f7ab89f9850b162f3f114/yarl-1.24.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf", size = 103010, upload-time = "2026-05-19T21:30:43.985Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d2/4597912315096f7bb359e46e13bf8b60994fcbb2db29b804c0902ef4eff5/yarl-1.24.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc", size = 101128, upload-time = "2026-05-19T21:30:46.291Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d5/c8e86e120521e646013d02a8e3b8884392e28494be8f392366e50d208efc/yarl-1.24.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb", size = 101382, upload-time = "2026-05-19T21:30:48.085Z" }, - { url = "https://files.pythonhosted.org/packages/fa/98/70b229236118f89dbeb739b76f10225bbf53b5497725502594c9a01d699a/yarl-1.24.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420", size = 95964, upload-time = "2026-05-19T21:30:49.785Z" }, - { url = "https://files.pythonhosted.org/packages/87/f8/56c386981e3c8648d279fdef2397ffec577e8320fd5649745e34d54faeb7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f", size = 106204, upload-time = "2026-05-19T21:30:51.862Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1e/765afe97811ca35933e2a7de70ac57b1997ea2e4ee895719ee7a231fb7e5/yarl-1.24.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa", size = 101510, upload-time = "2026-05-19T21:30:53.62Z" }, - { url = "https://files.pythonhosted.org/packages/ee/78/393913f4b9039e1edd09ae8a9bbb9d539be909a8abf6d8a2084585bed4b7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe", size = 105584, upload-time = "2026-05-19T21:30:55.962Z" }, - { url = "https://files.pythonhosted.org/packages/78/87/deb17b7049bbe74ea11a713b86f8f27800cc1c8648b0b797243ebb4830ba/yarl-1.24.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd", size = 103410, upload-time = "2026-05-19T21:30:57.962Z" }, - { url = "https://files.pythonhosted.org/packages/8f/be/f9f7594e23b5b93affff0318e4593c1920331bcaefda326cabcad94296a1/yarl-1.24.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215", size = 102980, upload-time = "2026-05-19T21:30:59.735Z" }, - { url = "https://files.pythonhosted.org/packages/65/a4/ba80dccd3593ff1f01051a818694d07b58cb8232677ee9a22a5a1f93a9fc/yarl-1.24.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d", size = 91219, upload-time = "2026-05-19T21:31:01.934Z" }, - { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" }, -] - -[[package]] -name = "youtube-transcript-api" -version = "1.2.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "defusedxml" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/60/43/4104185a2eaa839daa693b30e15c37e7e58795e8e09ec414f22b3db54bec/youtube_transcript_api-1.2.4.tar.gz", hash = "sha256:b72d0e96a335df599d67cee51d49e143cff4f45b84bcafc202ff51291603ddcd", size = 469839, upload-time = "2026-01-29T09:09:17.088Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/95/129ea37efd6cd6ed00f62baae6543345c677810b8a3bf0026756e1d3cf3c/youtube_transcript_api-1.2.4-py3-none-any.whl", hash = "sha256:03878759356da5caf5edac77431780b91448fb3d8c21d4496015bdc8a7bc43ff", size = 485227, upload-time = "2026-01-29T09:09:15.427Z" }, -] - [[package]] name = "zstandard" version = "0.25.0" From 5cf5724139e2704f9db667759d27915bcf3f9b61 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 02:30:31 -0500 Subject: [PATCH 15/35] test: enforce full workspace coverage --- .github/workflows/ci.yml | 2 +- AGENTS.md | 1 + docs/development.rst | 1 + packages/agentic-fabric/README.md | 1 + .../agentic_fabric/tools/scraping_tools.py | 2 - .../agentic-fabric/tests/test_cli_commands.py | 46 +++++++++++++++++ .../tests/test_connector_builder_crew.py | 22 +++++++++ .../tests/test_import_surfaces.py | 17 +++++++ .../tests/test_scraping_tool_crawler.py | 33 +++++++++++++ .../tests/test_single_agent_runners.py | 34 +++++++++++++ .../tests/test_tool_registry.py | 38 ++++++++++++++ .../tests/test_plugin.py | 49 +++++++++++++++++++ tox.ini | 16 ++++++ 13 files changed, 259 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 981d25a..60c59da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,4 +51,4 @@ jobs: enable-cache: true - run: uv sync --all-packages --dev - run: uv tool install tox --with tox-uv --with tox-gh - - run: tox -e lint,typecheck,plugin,docs,build + - run: tox -e lint,typecheck,coverage,plugin,docs,build diff --git a/AGENTS.md b/AGENTS.md index 0469660..b6980ce 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -81,6 +81,7 @@ uv sync --all-packages tox -e lint tox -e typecheck tox -e py311,py312,py313,py314 +tox -e coverage tox -e plugin tox -e docs tox -e build diff --git a/docs/development.rst b/docs/development.rst index 4d7aec8..63b9381 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -11,6 +11,7 @@ Use tox for local validation: tox -e py312 tox -e py313 tox -e py314 + tox -e coverage tox -e docs tox -e build diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index 8b48391..6629d6a 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -189,6 +189,7 @@ tox -e py311 tox -e py312 tox -e py313 tox -e py314 +tox -e coverage tox -e build ``` diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py b/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py index bf5c9ca..e6d4dbb 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py @@ -107,8 +107,6 @@ def _run(self, url: str) -> str: while urls_to_visit and len(visited_urls) < MAX_CRAWL_PAGES: current_url, depth = urls_to_visit.popleft() queued_urls.discard(current_url) - if current_url in visited_urls: - continue visited_urls.add(current_url) try: diff --git a/packages/agentic-fabric/tests/test_cli_commands.py b/packages/agentic-fabric/tests/test_cli_commands.py index f527823..1f8a05b 100644 --- a/packages/agentic-fabric/tests/test_cli_commands.py +++ b/packages/agentic-fabric/tests/test_cli_commands.py @@ -199,6 +199,30 @@ def test_cmd_run_json_reports_execution_errors( assert json.loads(capsys.readouterr().out)["error"] == "bad crew" +@pytest.mark.parametrize("use_json", [True, False]) +def test_cmd_run_reports_file_read_errors(capsys: pytest.CaptureFixture[str], use_json: bool) -> None: + """Multi-agent CLI runs should report unreadable input files.""" + args = SimpleNamespace( + json=use_json, + runner=None, + package="pkg", + crew="reviewer", + file="/definitely/missing/task.txt", + input=None, + framework="auto", + ) + + with pytest.raises(SystemExit) as exc_info: + cli.cmd_run(args) + + assert exc_info.value.code == 2 + output = capsys.readouterr().out + if use_json: + assert json.loads(output)["success"] is False + else: + assert "Error:" in output + + def test_cmd_run_text_reports_missing_arguments(capsys: pytest.CaptureFixture[str]) -> None: args = SimpleNamespace(json=False, runner=None, package=None, crew=None) @@ -534,6 +558,28 @@ def test_single_agent_text_requires_input(capsys: pytest.CaptureFixture[str]) -> assert "No input provided" in capsys.readouterr().out +@pytest.mark.parametrize("use_json", [True, False]) +def test_single_agent_reports_file_read_errors(capsys: pytest.CaptureFixture[str], use_json: bool) -> None: + """Single-agent CLI runs should report unreadable input files.""" + args = SimpleNamespace( + json=use_json, + runner="fake", + input=None, + file="/definitely/missing/task.txt", + package=None, + ) + + with pytest.raises(SystemExit) as exc_info: + cli._cmd_run_single_agent(args, use_json=use_json, start_time=0) + + assert exc_info.value.code == 2 + output = capsys.readouterr().out + if use_json: + assert json.loads(output)["success"] is False + else: + assert "Error:" in output + + def test_single_agent_json_reports_unavailable_runner(capsys: pytest.CaptureFixture[str]) -> None: args = SimpleNamespace(json=True, runner="fake", input="task", file=None, package=None, model=None) fake_runner = FakeCliRunner(available=False) diff --git a/packages/agentic-fabric/tests/test_connector_builder_crew.py b/packages/agentic-fabric/tests/test_connector_builder_crew.py index d376955..17a7e03 100644 --- a/packages/agentic-fabric/tests/test_connector_builder_crew.py +++ b/packages/agentic-fabric/tests/test_connector_builder_crew.py @@ -2,6 +2,7 @@ from __future__ import annotations +import builtins import importlib import sys import types @@ -120,3 +121,24 @@ def kickoff(self, inputs: dict[str, Any]) -> Any: crew_instance = module.ConnectorBuilderCrew() assert crew_instance.kickoff(inputs={"url": "http://example.com"}) == "raw success" + + +def test_connector_builder_reports_missing_crewai(monkeypatch: pytest.MonkeyPatch) -> None: + """Connector builder should raise install guidance when CrewAI is absent.""" + module = import_connector_builder_with_fake_crewai(monkeypatch) + monkeypatch.delitem(sys.modules, "crewai", raising=False) + module.Agent = None + module.Crew = None + module.Task = None + + original_import = builtins.__import__ + + def reject_crewai(name: str, *args: Any, **kwargs: Any) -> Any: + if name == "crewai": + raise ImportError("missing crewai") + return original_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", reject_crewai) + + with pytest.raises(RuntimeError, match="pip install crewai"): + module._load_crewai_classes() diff --git a/packages/agentic-fabric/tests/test_import_surfaces.py b/packages/agentic-fabric/tests/test_import_surfaces.py index 905d8bf..2183d4e 100644 --- a/packages/agentic-fabric/tests/test_import_surfaces.py +++ b/packages/agentic-fabric/tests/test_import_surfaces.py @@ -3,6 +3,7 @@ from __future__ import annotations import importlib +import importlib.metadata import runpy import sys import types @@ -10,6 +11,22 @@ import pytest +def test_package_version_falls_back_when_not_installed(monkeypatch: pytest.MonkeyPatch) -> None: + """Source-tree imports should have a stable version fallback.""" + package_file = importlib.import_module("agentic_fabric").__file__ + + def missing_version(name: str) -> str: + if name == "agentic-fabric": + raise importlib.metadata.PackageNotFoundError(name) + return "1.0.0" + + monkeypatch.setattr(importlib.metadata, "version", missing_version) + + namespace = runpy.run_path(package_file, run_name="agentic_fabric_version_fallback") + + assert namespace["__version__"] == "0.0.0" + + def test_base_exports_file_tools_with_fake_crewai(monkeypatch: pytest.MonkeyPatch) -> None: """The base package should re-export file tools when CrewAI is present.""" fake_crewai = types.ModuleType("crewai") diff --git a/packages/agentic-fabric/tests/test_scraping_tool_crawler.py b/packages/agentic-fabric/tests/test_scraping_tool_crawler.py index 0d0b09b..c0fb2d0 100644 --- a/packages/agentic-fabric/tests/test_scraping_tool_crawler.py +++ b/packages/agentic-fabric/tests/test_scraping_tool_crawler.py @@ -96,6 +96,39 @@ def test_crawler_enforces_page_limit(monkeypatch: pytest.MonkeyPatch) -> None: assert scraping_tools.requests.get.call_count == 3 +def test_crawler_enforces_depth_limit(monkeypatch: pytest.MonkeyPatch) -> None: + """Crawl depth should prevent child requests when configured to zero.""" + scraping_tools = import_scraping_tools(monkeypatch) + monkeypatch.setattr(scraping_tools, "MAX_CRAWL_DEPTH", 0) + root_response = MagicMock() + root_response.content = b"Page 1

root

" + root_response.raise_for_status.return_value = None + scraping_tools.requests.get.return_value = root_response + + result = scraping_tools.CrawlWebsiteTool()._run("https://example.test") + + assert "root" in result + assert scraping_tools.requests.get.call_count == 1 + + +def test_scrape_website_tool_success_and_failure(monkeypatch: pytest.MonkeyPatch) -> None: + """The first-party scrape tool should handle safe, unsafe, and failed URLs.""" + scraping_tools = import_scraping_tools(monkeypatch) + response = MagicMock() + response.content = b"

Visible content

" + response.raise_for_status.return_value = None + scraping_tools.requests.get.return_value = response + + assert "Visible content" in scraping_tools.ScrapeWebsiteTool()._run("https://example.test") + + scraping_tools.requests.get.reset_mock() + assert scraping_tools.ScrapeWebsiteTool()._run("http://127.0.0.1") == "" + scraping_tools.requests.get.assert_not_called() + + scraping_tools.requests.get.side_effect = scraping_tools.requests.RequestException("boom") + assert scraping_tools.ScrapeWebsiteTool()._run("https://example.test") == "" + + def test_scrape_content_removes_script_and_style_tags(monkeypatch: pytest.MonkeyPatch) -> None: """The scraper should remove script/style nodes before reading text.""" scraping_tools = import_scraping_tools(monkeypatch) diff --git a/packages/agentic-fabric/tests/test_single_agent_runners.py b/packages/agentic-fabric/tests/test_single_agent_runners.py index 9cff69e..4a30154 100644 --- a/packages/agentic-fabric/tests/test_single_agent_runners.py +++ b/packages/agentic-fabric/tests/test_single_agent_runners.py @@ -235,6 +235,8 @@ def test_rejects_shell_operator_in_command(self): @pytest.mark.parametrize( "field_name,value", [ + ("auto_approve", " "), + ("structured_output", '--json "unterminated'), ("task_flag", "--prompt; rm -rf /"), ("subcommand", "run && rm -rf /"), ("auto_approve", "--yes | sh"), @@ -328,6 +330,30 @@ def test_load_profiles_rejects_symlink_profiles_file(self, tmp_path: Path): with pytest.raises(FileNotFoundError, match="regular file"): LocalCLIRunner._load_profiles() + def test_profiles_source_rejects_unexpected_file_name(self, tmp_path: Path): + """The bundled profiles source should have the expected file name.""" + from agentic_fabric.runners import local_cli_runner + + unexpected_file = tmp_path / "profiles.yaml" + unexpected_file.write_text("profiles: {}\n", encoding="utf-8") + + with pytest.raises(ValueError, match="Unexpected profiles file name"): + local_cli_runner._validate_profiles_file_source(unexpected_file) + + def test_load_profiles_rejects_invalid_profile_entries(self, tmp_path: Path): + """Profiles YAML should contain string names mapped to config mappings.""" + profiles_file = tmp_path / "local_cli_profiles.yaml" + profiles_file.write_text("profiles:\n bad: not-a-mapping\n", encoding="utf-8") + + with patch("agentic_fabric.runners.local_cli_runner.Path") as mock_path: + mock_path.return_value.parent = tmp_path + mock_path.return_value.__truediv__.return_value = profiles_file + + LocalCLIRunner._profiles_cache = None + + with pytest.raises(TypeError, match="invalid profile entry"): + LocalCLIRunner._load_profiles() + def test_init_with_config_object(self): """Should initialize with a LocalCLIConfig object.""" config = LocalCLIConfig( @@ -400,6 +426,14 @@ def test_run_rejects_nul_task_argument(self): with pytest.raises(ValueError, match="NUL byte"): runner.run("safe\x00unsafe") + def test_run_rejects_non_string_runtime_arguments(self): + """Runtime arguments should be strings before reaching subprocess.""" + config = LocalCLIConfig(command="test-tool", task_flag="--task") + runner = LocalCLIRunner(config) + + with pytest.raises(TypeError, match="task must be a string"): + runner.run(123) # type: ignore[arg-type] + @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) @patch("subprocess.run") def test_run_success(self, mock_run: MagicMock): diff --git a/packages/agentic-fabric/tests/test_tool_registry.py b/packages/agentic-fabric/tests/test_tool_registry.py index 65b5cff..0860ee9 100644 --- a/packages/agentic-fabric/tests/test_tool_registry.py +++ b/packages/agentic-fabric/tests/test_tool_registry.py @@ -219,3 +219,41 @@ def test_vendor_capability_tools_reads_vendor_data_catalog(self) -> None: def test_vendor_capability_tools_returns_empty_without_vendor_catalog(self) -> None: """The import shim should not invent provider behavior.""" assert vendor_capability_tools(object()) == [] + + def test_vendor_capability_tools_creates_default_agentic_data(self, monkeypatch: pytest.MonkeyPatch) -> None: + """The vendor catalog helper should create AgenticData when none is supplied.""" + fake_data = MagicMock() + fake_data.capabilities.return_value = [{"provider": "slack", "operation": "list_messages"}] + + class FakeAgenticData: + def __new__(cls) -> MagicMock: + return fake_data + + monkeypatch.setattr("agentic_fabric.agentic_data.AgenticData", FakeAgenticData) + + tools = vendor_capability_tools(provider="slack") + + fake_data.capabilities.assert_called_once_with("slack", include_unavailable=True) + assert tools[0].provider == "slack" + + def test_builds_tool_from_getter_style_metadata(self) -> None: + """Capability metadata can expose values through a get method.""" + + class GetterMetadata: + def get(self, key: str, default: str) -> str: + values = {"provider": "aws", "operation": "get_secret"} + return values.get(key, default) + + tool = VendorCapabilityTool.from_metadata(GetterMetadata()) + + assert tool.provider == "aws" + assert tool.operation == "get_secret" + + def test_builds_tool_from_attribute_metadata(self) -> None: + """Capability metadata can expose values as attributes.""" + metadata = type("Metadata", (), {"provider": "github", "operation": "list_repos"})() + + tool = VendorCapabilityTool.from_metadata(metadata) + + assert tool.provider == "github" + assert tool.operation == "list_repos" diff --git a/packages/pytest-agentic-fabric/tests/test_plugin.py b/packages/pytest-agentic-fabric/tests/test_plugin.py index c14b18b..d9e7e3e 100644 --- a/packages/pytest-agentic-fabric/tests/test_plugin.py +++ b/packages/pytest-agentic-fabric/tests/test_plugin.py @@ -2,6 +2,10 @@ from __future__ import annotations +import importlib +import importlib.metadata +import runpy + from collections.abc import Callable from pathlib import Path from types import ModuleType @@ -13,6 +17,22 @@ pytest_plugins = ("pytester",) +def test_package_version_falls_back_when_not_installed(monkeypatch: pytest.MonkeyPatch) -> None: + """Source-tree imports should have a stable version fallback.""" + package_file = importlib.import_module("pytest_agentic_fabric").__file__ + + def missing_version(name: str) -> str: + if name == "pytest-agentic-fabric": + raise importlib.metadata.PackageNotFoundError(name) + return "1.0.0" + + monkeypatch.setattr(importlib.metadata, "version", missing_version) + + namespace = runpy.run_path(package_file, run_name="pytest_agentic_fabric_version_fallback") + + assert namespace["__version__"] == "0.0.0" + + def test_agentic_runtime_available_fixture(agentic_runtime_available: Callable[[str], bool]) -> None: """The runtime availability fixture should detect installed modules.""" assert agentic_runtime_available("sys") is True @@ -43,6 +63,18 @@ def test_agentic_mock_runtime_fixture(agentic_mock_runtime: Callable[[str], dict assert langgraph.prebuilt.create_react_agent("llm", [])["args"] == ("llm", []) +@pytest.mark.parametrize("runtime,expected_attr", [("crewai", "Agent"), ("strands", "Agent")]) +def test_agentic_mock_runtime_sets_runtime_entrypoints( + agentic_mock_runtime: Callable[[str], dict[str, ModuleType]], + runtime: str, + expected_attr: str, +) -> None: + """Runtime mocking fixture should install common runtime entrypoints.""" + modules = agentic_mock_runtime(runtime) + + assert hasattr(modules[runtime], expected_attr) + + def test_agentic_crew_config_fixture(agentic_crew_config: dict[str, Any]) -> None: """Minimal crew config should be usable by runtime tests.""" assert agentic_crew_config["agents"]["tester"]["role"] == "Tester" @@ -71,3 +103,20 @@ def test_live_runtime(): result = pytester.runpytest("-q") result.assert_outcomes(skipped=1) + + +def test_agentic_e2e_marker_runs_when_enabled(pytester: pytest.Pytester) -> None: + """Runtime-dependent tests should run when --agentic-e2e is passed.""" + pytester.makepyfile( + """ + import pytest + + @pytest.mark.agentic_e2e + def test_live_runtime(): + assert True + """ + ) + + result = pytester.runpytest("-q", "--agentic-e2e") + + result.assert_outcomes(passed=1) diff --git a/tox.ini b/tox.ini index 7bbdf86..216a612 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,7 @@ env_list = py312 py313 py314 + coverage plugin docs build @@ -59,6 +60,21 @@ deps = -e packages/pytest-agentic-fabric[tests] commands = pytest packages/pytest-agentic-fabric/tests {posargs} +[testenv:coverage] +description = Enforce full coverage for both publishable packages +runner = uv-venv-runner +package = skip +deps = + -e packages/agentic-fabric[tests] + -e packages/pytest-agentic-fabric[tests] +commands = + coverage erase + coverage run --source=agentic_fabric -m pytest packages/agentic-fabric/tests + coverage report --show-missing --skip-covered --fail-under=100 + coverage erase + coverage run --source=pytest_agentic_fabric -m pytest packages/pytest-agentic-fabric/tests + coverage report --show-missing --skip-covered --fail-under=100 + [testenv:docs] description = Build Sphinx docs and generated autodoc2 API reference runner = uv-venv-runner From 17277a6ce22758019bf887ca45e70daca3fd4525 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 02:37:59 -0500 Subject: [PATCH 16/35] ci: align release coverage gate --- .github/workflows/cd.yml | 2 +- .../src/agentic_fabric/runners/local_cli_runner.py | 1 + packages/agentic-fabric/tests/test_single_agent_runners.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index a346e27..8e9aba6 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -59,7 +59,7 @@ jobs: enable-cache: true - run: uv sync --all-packages --dev - run: uv tool install tox --with tox-uv --with tox-gh - - run: tox -e lint,typecheck,plugin,docs,build + - run: tox -e lint,typecheck,coverage,plugin,docs,build publish-package: name: Publish package to PyPI diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py index 877d2de..61790b2 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/local_cli_runner.py @@ -405,6 +405,7 @@ def run( cwd=working_dir, capture_output=True, text=True, + shell=False, timeout=self.config.timeout, env=os.environ.copy(), # Pass through environment check=True, diff --git a/packages/agentic-fabric/tests/test_single_agent_runners.py b/packages/agentic-fabric/tests/test_single_agent_runners.py index 4a30154..06bd790 100644 --- a/packages/agentic-fabric/tests/test_single_agent_runners.py +++ b/packages/agentic-fabric/tests/test_single_agent_runners.py @@ -460,6 +460,7 @@ def test_run_success(self, mock_run: MagicMock): # Check command was built correctly call_args = mock_run.call_args assert call_args[0][0] == ["test-tool", "--task", "Fix the bug"] + assert call_args.kwargs["shell"] is False @patch.dict("os.environ", {"TEST_API_KEY": "test-key"}) @patch("subprocess.run") From fc5e82cde63dba4c5289d2764006924c8072a092 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 02:54:55 -0500 Subject: [PATCH 17/35] fix: keep file tools framework neutral --- docs/getting-started.rst | 7 +- packages/agentic-fabric/README.md | 5 +- packages/agentic-fabric/pyproject.toml | 1 - .../src/agentic_fabric/tools/file_tools.py | 88 ++++++++++++++----- .../agentic-fabric/tests/test_file_tools.py | 37 ++++---- .../tests/test_import_surfaces.py | 14 +-- .../tests/test_tool_adapters.py | 13 +-- uv.lock | 2 - 8 files changed, 104 insertions(+), 63 deletions(-) diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 930c5b8..0ce3949 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -2,7 +2,8 @@ Getting Started =============== Install the core package when you only need discovery, configuration loading, -lazy runner selection, and local CLI runner profiles: +lazy runner selection, local CLI runner profiles, and framework-neutral file +tools: .. code:: bash @@ -31,6 +32,10 @@ executables that you install separately: agentic-fabric list-runners --json agentic-fabric run --runner aider --input "Add validation to auth.py" +Built-in filesystem tools also stay in the core package without depending on +CrewAI or Pydantic. Runtime adapters add framework-specific wrapping only when +their optional dependencies are installed. + .. code:: python from agentic_fabric import detect_framework, get_framework_info, get_runner diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index 6629d6a..a2c8381 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -15,7 +15,7 @@ from what is installed. ## Installation ```bash -# Core discovery, loading, and runner selection +# Core discovery, loading, runner selection, local CLI, and neutral file tools pip install agentic-fabric # With a specific framework @@ -112,6 +112,9 @@ result = session.run_agent("reviewer", runtime="crewai") - Framework agnostic: one crew definition, multiple runtime backends. - Lazy imports: core package import does not require CrewAI, LangGraph, Strands, or vendor SDKs. +- Framework-neutral file tools: built-in filesystem tools can be resolved + without installing CrewAI or Pydantic; framework adapters add schema wrappers + only when their optional dependencies are present. - Focused extras: `langgraph`, `strands`, `mcp`, `scraping`, `tests`, `typing`, `docs`, and `dev`. - `AgenticData`: carries data, registered crews, active runtime selection, and diff --git a/packages/agentic-fabric/pyproject.toml b/packages/agentic-fabric/pyproject.toml index beb8dfc..45f0a26 100644 --- a/packages/agentic-fabric/pyproject.toml +++ b/packages/agentic-fabric/pyproject.toml @@ -24,7 +24,6 @@ classifiers = [ "Typing :: Typed", ] dependencies = [ - "pydantic>=2.13.4,<3.0.0", "pyyaml>=6.0.3", ] diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py b/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py index ce3969a..7e4e63f 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/file_tools.py @@ -1,4 +1,4 @@ -"""File manipulation tools for CrewAI agents. +"""Framework-neutral file manipulation tools for agents. These tools enable agents to read and write code to specific directories in workspace package codebases. @@ -9,9 +9,27 @@ import os from pathlib import Path +from typing import Any -from crewai.tools import BaseTool -from pydantic import BaseModel, Field + +try: # pragma: no cover - exercised by consumers that install CrewAI + from crewai.tools import BaseTool as _BaseTool +except ImportError: + + class _BaseTool: # type: ignore[no-redef] + """Minimal base class used when no framework-specific tool base exists.""" + + +def _load_pydantic_schema_helpers() -> tuple[Any, Any]: # pragma: no cover + """Return Pydantic helpers when optional schema metadata is available.""" + try: + from pydantic import BaseModel, Field + except ImportError: + return None, None + return BaseModel, Field + + +_PYDANTIC_BASE_MODEL, _PYDANTIC_FIELD = _load_pydantic_schema_helpers() def _find_workspace_root() -> Path | None: @@ -109,14 +127,34 @@ def _is_allowed_write_path(clean_path: str) -> bool: return any(clean_path == allowed_dir or clean_path.startswith(f"{allowed_dir}/") for allowed_dir in ALLOWED_WRITE_DIRS) -class WriteFileInput(BaseModel): - """Input schema for GameCodeWriterTool.""" - - file_path: str = Field(description="Relative path from workspace root (e.g., 'src/ecs/data/NewComponent.ts')") - content: str = Field(description="The TypeScript/TSX code content to write") - - -class GameCodeWriterTool(BaseTool): +def _build_pydantic_schema( + name: str, + doc: str, + fields_by_description: dict[str, str], +) -> Any: # pragma: no cover + """Build optional Pydantic schemas without making Pydantic a core dependency.""" + if _PYDANTIC_BASE_MODEL is None or _PYDANTIC_FIELD is None: + return None + namespace: dict[str, Any] = { + "__annotations__": dict.fromkeys(fields_by_description, str), + "__doc__": doc, + } + for field_name, description in fields_by_description.items(): + namespace[field_name] = _PYDANTIC_FIELD(description=description) + return type(name, (_PYDANTIC_BASE_MODEL,), namespace) + + +WriteFileInput = _build_pydantic_schema( + "WriteFileInput", + "Input schema for GameCodeWriterTool.", + { + "file_path": "Relative path from workspace root (e.g., 'src/ecs/data/NewComponent.ts')", + "content": "The TypeScript/TSX code content to write", + }, +) + + +class GameCodeWriterTool(_BaseTool): # type: ignore[misc,valid-type] """Tool for writing code files to a game package codebase. This tool is restricted to specific directories to ensure agents @@ -146,7 +184,7 @@ class GameCodeWriterTool(BaseTool): file_path: "src/ecs/data/species.ts" content: "export const PREDATOR_SPECIES = { ... }" """ - args_schema: type[BaseModel] = WriteFileInput + args_schema: Any = WriteFileInput def _run(self, file_path: str, content: str) -> str: """Write the file content to the specified path.""" @@ -179,13 +217,14 @@ def _run(self, file_path: str, content: str) -> str: return f"Error writing file: {e!s}" -class ReadFileInput(BaseModel): - """Input schema for GameCodeReaderTool.""" +ReadFileInput = _build_pydantic_schema( + "ReadFileInput", + "Input schema for GameCodeReaderTool.", + {"file_path": "Relative path from workspace root (e.g., 'src/ecs/components.ts')"}, +) - file_path: str = Field(description="Relative path from workspace root (e.g., 'src/ecs/components.ts')") - -class GameCodeReaderTool(BaseTool): +class GameCodeReaderTool(_BaseTool): # type: ignore[misc,valid-type] """Tool for reading code files from a game package codebase. Use this to understand existing patterns before writing new code. @@ -205,7 +244,7 @@ class GameCodeReaderTool(BaseTool): Example: file_path: "src/ecs/components.ts" """ - args_schema: type[BaseModel] = ReadFileInput + args_schema: Any = ReadFileInput def _run(self, file_path: str) -> str: """Read the file content from the specified path.""" @@ -233,13 +272,14 @@ def _run(self, file_path: str) -> str: return f"Error reading file: {e!s}" -class ListDirInput(BaseModel): - """Input schema for DirectoryListTool.""" - - directory: str = Field(description="Relative directory path from workspace root (e.g., 'src/ecs')") +ListDirInput = _build_pydantic_schema( + "ListDirInput", + "Input schema for DirectoryListTool.", + {"directory": "Relative directory path from workspace root (e.g., 'src/ecs')"}, +) -class DirectoryListTool(BaseTool): +class DirectoryListTool(_BaseTool): # type: ignore[misc,valid-type] """Tool for listing files in a directory. Use this to discover existing files and understand project structure. @@ -259,7 +299,7 @@ class DirectoryListTool(BaseTool): Example: directory: "src/ecs/data" """ - args_schema: type[BaseModel] = ListDirInput + args_schema: Any = ListDirInput def _run(self, directory: str) -> str: """List directory contents.""" diff --git a/packages/agentic-fabric/tests/test_file_tools.py b/packages/agentic-fabric/tests/test_file_tools.py index 10a4735..00e65f0 100644 --- a/packages/agentic-fabric/tests/test_file_tools.py +++ b/packages/agentic-fabric/tests/test_file_tools.py @@ -1,13 +1,10 @@ -"""Tests for file manipulation tools. - -Note: These tests require crewai to be installed for the tool base classes. -""" +"""Tests for framework-neutral file manipulation tools.""" from __future__ import annotations +import builtins import os import sys -import types from pathlib import Path from unittest.mock import patch @@ -16,22 +13,30 @@ @pytest.fixture(autouse=True) -def fake_crewai_base_tool(monkeypatch: pytest.MonkeyPatch): - """Import file_tools without requiring the optional CrewAI package.""" - fake_crewai = types.ModuleType("crewai") - fake_tools = types.ModuleType("crewai.tools") - - class BaseTool: - """Minimal stand-in for crewai.tools.BaseTool.""" - - fake_tools.BaseTool = BaseTool - monkeypatch.setitem(sys.modules, "crewai", fake_crewai) - monkeypatch.setitem(sys.modules, "crewai.tools", fake_tools) +def fresh_file_tools_module(): + """Keep import-branch assertions isolated between tests.""" sys.modules.pop("agentic_fabric.tools.file_tools", None) yield sys.modules.pop("agentic_fabric.tools.file_tools", None) +def test_file_tools_import_without_optional_frameworks(monkeypatch: pytest.MonkeyPatch) -> None: + """Built-in tools should not require CrewAI or Pydantic at import time.""" + real_import = builtins.__import__ + + def block_optional_imports(name: str, *args, **kwargs): + if name in {"crewai.tools", "pydantic"}: + raise ImportError(name) + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", block_optional_imports) + + from agentic_fabric.tools.file_tools import GameCodeReaderTool, GameCodeWriterTool + + assert GameCodeReaderTool().args_schema is None + assert GameCodeWriterTool()._run("forbidden/example.ts", "").startswith("Error:") + + class TestGetWorkspaceRoot: """Tests for get_workspace_root function.""" diff --git a/packages/agentic-fabric/tests/test_import_surfaces.py b/packages/agentic-fabric/tests/test_import_surfaces.py index 2183d4e..8e9ae92 100644 --- a/packages/agentic-fabric/tests/test_import_surfaces.py +++ b/packages/agentic-fabric/tests/test_import_surfaces.py @@ -6,7 +6,6 @@ import importlib.metadata import runpy import sys -import types import pytest @@ -27,17 +26,8 @@ def missing_version(name: str) -> str: assert namespace["__version__"] == "0.0.0" -def test_base_exports_file_tools_with_fake_crewai(monkeypatch: pytest.MonkeyPatch) -> None: - """The base package should re-export file tools when CrewAI is present.""" - fake_crewai = types.ModuleType("crewai") - fake_tools = types.ModuleType("crewai.tools") - - class BaseTool: - """Minimal BaseTool stand-in.""" - - fake_tools.BaseTool = BaseTool - monkeypatch.setitem(sys.modules, "crewai", fake_crewai) - monkeypatch.setitem(sys.modules, "crewai.tools", fake_tools) +def test_base_exports_file_tools_without_crewai() -> None: + """The base package should re-export framework-neutral file tools.""" sys.modules.pop("agentic_fabric.tools.file_tools", None) sys.modules.pop("agentic_fabric.base", None) diff --git a/packages/agentic-fabric/tests/test_tool_adapters.py b/packages/agentic-fabric/tests/test_tool_adapters.py index 7d0625a..9d46b85 100644 --- a/packages/agentic-fabric/tests/test_tool_adapters.py +++ b/packages/agentic-fabric/tests/test_tool_adapters.py @@ -9,8 +9,6 @@ import pytest -from pydantic import BaseModel - from agentic_fabric.tools.adapters import ( _build_runner, _invoke_tool, @@ -20,10 +18,13 @@ ) -class WriteFileArgs(BaseModel): +class WriteFileArgs: """Schema used to verify adapter schema forwarding.""" - file_path: str + @classmethod + def model_json_schema(cls) -> dict[str, str]: + """Return a minimal schema compatible with adapter expectations.""" + return {"title": "WriteFileArgs"} class DummyTool: @@ -69,7 +70,7 @@ def __init__( func, name: str, description: str, - args_schema: type[BaseModel] | None, + args_schema: type[WriteFileArgs] | None, infer_schema: bool, ) -> None: self.func = func @@ -85,7 +86,7 @@ def from_function( func, name: str, description: str, - args_schema: type[BaseModel] | None, + args_schema: type[WriteFileArgs] | None, infer_schema: bool, ) -> FakeStructuredTool: return cls( diff --git a/uv.lock b/uv.lock index ace7f69..5178e30 100644 --- a/uv.lock +++ b/uv.lock @@ -38,7 +38,6 @@ name = "agentic-fabric" version = "1.0.0" source = { editable = "packages/agentic-fabric" } dependencies = [ - { name = "pydantic" }, { name = "pyyaml" }, ] @@ -103,7 +102,6 @@ requires-dist = [ { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.0.0" }, { name = "mypy", marker = "extra == 'typing'", specifier = ">=1.20.1" }, { name = "myst-parser", marker = "extra == 'docs'", specifier = ">=4.0.1,<6.0.0" }, - { name = "pydantic", specifier = ">=2.13.4,<3.0.0" }, { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.0.3" }, { name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=0.23.0" }, { name = "pytest-cov", marker = "extra == 'tests'", specifier = ">=7.1.0" }, From eaad90eefedd29f6e98fff68f9d8e2ae621beb0b Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 02:59:57 -0500 Subject: [PATCH 18/35] refactor: centralize runtime requirement resolution --- .../src/agentic_fabric/core/decomposer.py | 59 +++++++------------ .../tests/test_decomposer_edge_cases.py | 5 ++ 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py b/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py index fdfaeb6..6c4a7a0 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py @@ -199,6 +199,26 @@ def is_cli_runner_available(profile: str) -> bool: return False +def _resolve_required_framework(crew_config: dict[str, Any], framework: str | None = None) -> str | None: + """Return the framework selected after honoring crew-level requirements.""" + required_framework = crew_config.get("required_framework") + if not required_framework: + return framework + + if framework and framework not in (required_framework, "auto"): + raise ValueError( + f"Crew requires {required_framework} (defined in .{required_framework}/ directory) " + f"but {framework} was requested" + ) + + if not is_framework_available(required_framework): + raise RuntimeError( + f"Crew requires {required_framework} but it's not installed. " + f"Install with: {_get_install_command(required_framework)}" + ) + return required_framework + + def decompose_crew( crew_config: dict[str, Any], framework: str | None = None, @@ -219,25 +239,7 @@ def decompose_crew( Raises: RuntimeError: If required framework is not available. """ - # Check if crew config requires a specific framework - required_framework = crew_config.get("required_framework") - - if required_framework: - # Framework is enforced by config directory (.crewai, .strands, etc.) - if framework and framework not in (required_framework, "auto"): - raise ValueError( - f"Crew requires {required_framework} (defined in .{required_framework}/ directory) " - f"but {framework} was requested" - ) - - if not is_framework_available(required_framework): - raise RuntimeError( - f"Crew requires {required_framework} but it's not installed. " - f"Install with: {_get_install_command(required_framework)}" - ) - framework = required_framework - - runner = get_runner(framework) + runner = get_runner(_resolve_required_framework(crew_config, framework)) return runner.build_crew(crew_config) @@ -271,23 +273,6 @@ def run_crew_auto( RuntimeError: If required framework is not available. ValueError: If requested framework conflicts with required framework. """ - # Check if crew config requires a specific framework - required_framework = crew_config.get("required_framework") - - if required_framework: - if framework and framework not in (required_framework, "auto"): - raise ValueError( - f"Crew requires {required_framework} (defined in .{required_framework}/ directory) " - f"but {framework} was requested" - ) - - if not is_framework_available(required_framework): - raise RuntimeError( - f"Crew requires {required_framework} but it's not installed. " - f"Install with: {_get_install_command(required_framework)}" - ) - framework = required_framework - - runner = get_runner(framework) + runner = get_runner(_resolve_required_framework(crew_config, framework)) crew = runner.build_crew(crew_config) return runner.run(crew, inputs or {}) diff --git a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py index 27938ee..bef74aa 100644 --- a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py +++ b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py @@ -9,6 +9,7 @@ from agentic_fabric.core.decomposer import ( _framework_cache, _get_install_command, + _resolve_required_framework, decompose_crew, detect_framework, get_available_frameworks, @@ -166,6 +167,10 @@ def mock_available(framework): class TestRunnerDispatch: """Tests for framework runner dispatch helpers.""" + def test_resolve_required_framework_preserves_requested_runtime_without_requirement(self) -> None: + """Framework selection should pass through when configs are runtime-agnostic.""" + assert _resolve_required_framework({"name": "reviewer"}, "langgraph") == "langgraph" + def test_get_runner_auto_detects_and_instantiates_runner(self, monkeypatch: pytest.MonkeyPatch) -> None: """get_runner should instantiate the registered runner class.""" From ceecb7f2fa809629158181f1a075d2d4c17fad7e Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 03:40:55 -0500 Subject: [PATCH 19/35] refactor: align fabric agent surfaces --- AGENTIC_REINFORCEMENT.md | 4 +- AGENTS.md | 2 +- .../{agentic-crew.css => agentic-fabric.css} | 0 docs/agentic-workflows.rst | 36 +- docs/architecture.rst | 12 +- docs/development.rst | 4 +- docs/getting-started.rst | 12 +- docs/index.rst | 4 +- docs/pillars.rst | 4 +- docs/vendor-fabric.rst | 9 +- packages/agentic-fabric/CHANGELOG.md | 5 +- packages/agentic-fabric/README.md | 56 ++- packages/agentic-fabric/examples/README.md | 2 +- .../examples/discovery_workflow.py | 24 +- .../examples/runtime_context.py | 4 +- .../packages/review/.crew/manifest.yaml | 7 - .../implementation_review/agents.yaml | 0 .../implementation_review/tasks.yaml | 0 .../packages/review/.fabric/manifest.yaml | 7 + packages/agentic-fabric/pyproject.toml | 6 +- .../src/agentic_fabric/__init__.py | 40 +- .../src/agentic_fabric/agentic_data.py | 96 ++-- .../src/agentic_fabric/base/archetypes.yaml | 2 +- .../src/agentic_fabric/core/__init__.py | 14 +- .../src/agentic_fabric/core/decomposer.py | 52 +- .../src/agentic_fabric/core/discovery.py | 95 ++-- .../src/agentic_fabric/core/loader.py | 12 +- .../src/agentic_fabric/core/manager.py | 111 ++--- .../src/agentic_fabric/core/runner.py | 54 +- .../src/agentic_fabric/crews/__init__.py | 1 - .../crews/connector_builder/__init__.py | 1 - .../agentic_fabric/fabric_agents/__init__.py | 1 + .../connector_builder/__init__.py | 1 + .../connector_builder/config/agents.yaml | 0 .../connector_builder/config/tasks.yaml | 0 .../connector_builder_fabric.py} | 35 +- .../agentic-fabric/src/agentic_fabric/main.py | 126 ++--- .../src/agentic_fabric/runners/__init__.py | 8 +- .../src/agentic_fabric/runners/base.py | 78 +-- .../agentic_fabric/runners/crewai_runner.py | 32 +- .../runners/langgraph_runner.py | 28 +- .../runners/single_agent_runner.py | 4 +- .../agentic_fabric/runners/strands_runner.py | 48 +- .../src/agentic_fabric/tools/__init__.py | 8 +- .../src/agentic_fabric/tools/meshy_mcp.py | 203 ++++++++ .../src/agentic_fabric/tools/registry.py | 4 +- .../src/agentic_fabric/tools/vendor_mcp.py | 330 +++++++++++++ .../{_crew_mocker.py => _fabric_mocker.py} | 50 +- packages/agentic-fabric/tests/conftest.py | 62 +-- packages/agentic-fabric/tests/e2e/README.md | 12 +- packages/agentic-fabric/tests/e2e/__init__.py | 2 +- packages/agentic-fabric/tests/e2e/conftest.py | 8 +- .../tests/e2e/test_crewai_e2e.py | 60 +-- .../tests/e2e/test_langgraph_e2e.py | 26 +- .../tests/e2e/test_strands_e2e.py | 30 +- .../agentic-fabric/tests/test_agentic_data.py | 60 +-- .../agentic-fabric/tests/test_capabilities.py | 14 +- .../agentic-fabric/tests/test_cli_commands.py | 100 ++-- .../agentic-fabric/tests/test_cli_smoke.py | 26 +- ...ew.py => test_connector_builder_fabric.py} | 36 +- .../agentic-fabric/tests/test_core_runner.py | 48 +- .../agentic-fabric/tests/test_crew_mocker.py | 73 --- .../tests/test_decomposer_edge_cases.py | 50 +- .../agentic-fabric/tests/test_discovery.py | 160 +++--- .../tests/test_discovery_nested.py | 72 +-- .../agentic-fabric/tests/test_examples.py | 14 +- .../tests/test_fabric_mocker.py | 73 +++ .../tests/test_import_surfaces.py | 2 +- packages/agentic-fabric/tests/test_loader.py | 50 +- .../agentic-fabric/tests/test_loader_unit.py | 26 +- packages/agentic-fabric/tests/test_manager.py | 128 ++--- .../agentic-fabric/tests/test_mcp_adapters.py | 464 ++++++++++++++++++ packages/agentic-fabric/tests/test_runners.py | 438 ++++++++--------- .../agentic-fabric/tests/test_tools_init.py | 31 +- .../tests/test_yaml_config_errors.py | 130 ++--- packages/pytest-agentic-fabric/README.md | 4 +- packages/pytest-agentic-fabric/pyproject.toml | 2 + .../src/pytest_agentic_fabric/plugin.py | 30 +- .../tests/test_plugin.py | 16 +- pyproject.toml | 16 + uv.lock | 3 + 81 files changed, 2467 insertions(+), 1431 deletions(-) rename docs/_static/{agentic-crew.css => agentic-fabric.css} (100%) delete mode 100644 packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/manifest.yaml rename packages/agentic-fabric/examples/sample_workspace/packages/review/{.crew/crews => .fabric/fabric_agents}/implementation_review/agents.yaml (100%) rename packages/agentic-fabric/examples/sample_workspace/packages/review/{.crew/crews => .fabric/fabric_agents}/implementation_review/tasks.yaml (100%) create mode 100644 packages/agentic-fabric/examples/sample_workspace/packages/review/.fabric/manifest.yaml delete mode 100644 packages/agentic-fabric/src/agentic_fabric/crews/__init__.py delete mode 100644 packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/__init__.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/fabric_agents/__init__.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/fabric_agents/connector_builder/__init__.py rename packages/agentic-fabric/src/agentic_fabric/{crews => fabric_agents}/connector_builder/config/agents.yaml (100%) rename packages/agentic-fabric/src/agentic_fabric/{crews => fabric_agents}/connector_builder/config/tasks.yaml (100%) rename packages/agentic-fabric/src/agentic_fabric/{crews/connector_builder/connector_builder_crew.py => fabric_agents/connector_builder/connector_builder_fabric.py} (71%) create mode 100644 packages/agentic-fabric/src/agentic_fabric/tools/meshy_mcp.py create mode 100644 packages/agentic-fabric/src/agentic_fabric/tools/vendor_mcp.py rename packages/agentic-fabric/tests/{_crew_mocker.py => _fabric_mocker.py} (92%) rename packages/agentic-fabric/tests/{test_connector_builder_crew.py => test_connector_builder_fabric.py} (74%) delete mode 100644 packages/agentic-fabric/tests/test_crew_mocker.py create mode 100644 packages/agentic-fabric/tests/test_fabric_mocker.py create mode 100644 packages/agentic-fabric/tests/test_mcp_adapters.py diff --git a/AGENTIC_REINFORCEMENT.md b/AGENTIC_REINFORCEMENT.md index 3b5d1d5..ef6bc3b 100644 --- a/AGENTIC_REINFORCEMENT.md +++ b/AGENTIC_REINFORCEMENT.md @@ -6,7 +6,7 @@ This repository creates the runtime- and agent-aware superclass on top of ## AgenticData Contract - `AgenticData` extends `VendorData`, not `ExtendedData` directly. -- Preserve vendor-layer semantics while adding runtime context, agent registry +- Preserve vendor-layer semantics while adding runtime context, fabric agent registry behavior, runner dispatch, and agent-facing tool exposure. - The local `_VendorDataBase` fallback is an importability shim, not a second architecture. Keep it minimal and temporary in spirit. @@ -17,7 +17,7 @@ This repository creates the runtime- and agent-aware superclass on top of - runner registries and runtime adapters - LangChain, CrewAI, LangGraph, Strands, and MCP-facing tool wrappers - agent-facing tool catalogs built from vendor capabilities -- crew, session, and agent orchestration context +- fabric agent, session, and agent orchestration context ## What This Repository Does Not Own diff --git a/AGENTS.md b/AGENTS.md index b6980ce..977f7d5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,7 @@ and remote state before finalizing this repo. `agentic-fabric` owns: -- framework-agnostic crew discovery and loading +- framework-agnostic fabric agent discovery and loading - runner adapters for CrewAI, LangGraph, Strands, and local CLI tools - agent-facing tool resolution and framework adapter utilities - planned optional passthrough dependencies to `vendor-fabric` after diff --git a/docs/_static/agentic-crew.css b/docs/_static/agentic-fabric.css similarity index 100% rename from docs/_static/agentic-crew.css rename to docs/_static/agentic-fabric.css diff --git a/docs/agentic-workflows.rst b/docs/agentic-workflows.rst index 0ffaea1..6441818 100644 --- a/docs/agentic-workflows.rst +++ b/docs/agentic-workflows.rst @@ -1,29 +1,29 @@ Agentic Workflows ================= -Crew definitions are YAML-first and runtime-neutral unless placed in a +Fabric agent definitions are YAML-first and runtime-neutral unless placed in a framework-specific directory. .. code:: yaml name: example - crews: + fabric_agents: reviewer: description: Review implementation quality agents: config/agents.yaml tasks: config/tasks.yaml -``agentic-fabric`` resolves the crew, selects a runner, and delegates +``agentic-fabric`` resolves the fabric agent, selects a runner, and delegates execution to the installed framework. The public API is intentionally small: .. code:: python - from agentic_fabric import discover_packages, get_crew_config, run_crew_auto + from agentic_fabric import discover_packages, get_fabric_agent_config, run_fabric_agent_auto packages = discover_packages() - config = get_crew_config(packages["example"], "reviewer") - result = run_crew_auto(config, inputs={"topic": "release readiness"}) + config = get_fabric_agent_config(packages["example"], "reviewer") + result = run_fabric_agent_auto(config, inputs={"topic": "release readiness"}) For stateful orchestration, register the same config with ``AgenticData``: @@ -32,7 +32,7 @@ For stateful orchestration, register the same config with from agentic_fabric import AgenticData - session = AgenticData(agent_registry={"reviewer": config}) + session = AgenticData(fabric_agents={"reviewer": config}) session.use_runtime("langgraph", strict=False) result = session.run_reviewer({"topic": "release readiness"}) @@ -70,7 +70,7 @@ executables and do not require a Python extra: Tool Resolution --------------- -Crew tool entries are resolved lazily. Built-in filesystem tool aliases, +Fabric agent tool entries are resolved lazily. Built-in filesystem tool aliases, ``mcp://filesystem/...`` aliases, and ``vendor://provider/operation`` references do not import optional framework or vendor packages until the tool is used. @@ -92,6 +92,24 @@ module imports must be explicitly allowlisted: export AGENTIC_FABRIC_TOOL_IMPORT_ALLOWLIST="my_company.tools,shared_agents." Manifest paths for ``agents``, ``tasks``, and ``knowledge`` are resolved -relative to the crew config directory and cannot escape it. Filesystem +relative to the fabric agent config directory and cannot escape it. Filesystem tools resolve symlinks before reading or writing and keep writes inside their configured workspace directories. + +MCP Adapters +------------ + +The ``mcp`` extra installs the MCP transport dependency. Provider +implementation still belongs to ``vendor-fabric``; this package only owns the +runtime-visible adapter layer: + +.. code:: bash + + agentic-fabric-vendor-mcp + agentic-fabric-meshy-mcp + +``agentic-fabric-vendor-mcp`` exposes credential-free vendor catalog tools and +public ``vendor-fabric`` data methods. ``agentic-fabric-meshy-mcp`` adapts the +Meshy capability definitions from ``vendor_fabric.meshy.tools`` into MCP tool +metadata. Install the matching ``vendor-fabric`` package and provider extras +in the same environment before running provider-backed MCP tools. diff --git a/docs/architecture.rst b/docs/architecture.rst index 8016bdf..ea72490 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -7,10 +7,10 @@ source of truth for agent runtime architecture in this repository. Boundary -------- -``agentic-fabric`` owns agent and crew orchestration: +``agentic-fabric`` owns agent and fabric agent orchestration: - discovery and manifest loading -- universal crew decomposition +- universal fabric agent composition - runner adapters for optional agent frameworks - tool resolution for built-in, MCP-style, fully qualified, and vendor-backed tools @@ -52,8 +52,8 @@ together: .. code:: python - session.register_agent("repo_summarizer", crew_config) - result = session.run_agent("repo_summarizer", repo="jbcom/extended-data") + session.register_fabric_agent("repo_summarizer", fabric_agent_config) + result = session.run_fabric_agent("repo_summarizer", repo="jbcom/extended-data") Agent Capability Registry ------------------------- @@ -97,7 +97,7 @@ Runtime selection should follow explicit precedence when the caller does not choose a runtime: 1. a runtime already active on ``AgenticData`` -2. a runtime requested in the crew manifest +2. a runtime requested in the fabric agent manifest 3. the first installed runtime in documented priority order 4. a clear unavailable-runtime error with install guidance @@ -121,7 +121,7 @@ Testing Package ``pytest-agentic-fabric`` is a sibling package in this repository. It provides runtime availability fixtures, runtime module mocking, minimal -crew/workspace fixtures, and opt-in E2E controls. +fabric agent/workspace fixtures, and opt-in E2E controls. Provider fixtures belong in ``pytest-vendor-fabric``. Base data fixtures belong in ``pytest-extended-data``. diff --git a/docs/development.rst b/docs/development.rst index 63b9381..8d6488d 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -22,9 +22,9 @@ Use ``pytest-agentic-fabric`` fixtures for reusable runtime test setup: .. code:: python - def test_runtime(agentic_mock_runtime, agentic_crew_config): + def test_runtime(agentic_mock_runtime, agentic_fabric_agent_config): agentic_mock_runtime("langgraph") - assert agentic_crew_config["name"] == "test_crew" + assert agentic_fabric_agent_config["name"] == "test_fabric_agent" Before deleting old monorepo code, first prove this repository owns the moved surface with docs, tests, package metadata, and release workflows. diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 0ce3949..f616c26 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -44,26 +44,26 @@ their optional dependencies are installed. framework = detect_framework() runner = get_runner(framework) -Crew definitions can live in ``.crew/``, ``.crewai/``, ``.langgraph/``, -or ``.strands/`` directories. A ``.crew/`` directory is +Fabric agent definitions can live in ``.fabric/``, ``.crewai/``, ``.langgraph/``, +or ``.strands/`` directories. A ``.fabric/`` directory is framework-agnostic. A framework-specific directory requires that runtime. If a required runtime is unavailable, runtime errors include the matching install guidance. Automatic selection follows CrewAI, then LangGraph, then -Strands, but explicit runtime selection always wins unless the crew is stored +Strands, but explicit runtime selection always wins unless the fabric agent is stored in a framework-specific directory. Use ``AgenticData`` when runtime context should travel with data and -registered crew definitions: +registered fabric agent definitions: .. code:: python from agentic_fabric import AgenticData session = AgenticData({"repo": "jbcom/agentic-fabric"}) - session.register_agent("reviewer", crew_config) - result = session.run_agent("reviewer", runtime="crewai") + session.register_fabric_agent("reviewer", fabric_agent_config) + result = session.run_fabric_agent("reviewer", runtime="crewai") CrewAI Dependency Note ---------------------- diff --git a/docs/index.rst b/docs/index.rst index d77732b..e63b8ac 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,8 +2,8 @@ Agentic Fabric ============== ``agentic-fabric`` is the standalone orchestration package for -framework-agnostic agent crews. It discovers YAML crew definitions, -selects an installed runtime, and runs the same crew on CrewAI, +framework-agnostic agent fabrics. It discovers YAML fabric agent definitions, +selects an installed runtime, and runs the same fabric agent on CrewAI, LangGraph, Strands, or local CLI runners. .. code:: bash diff --git a/docs/pillars.rst b/docs/pillars.rst index 990083a..a7370bb 100644 --- a/docs/pillars.rst +++ b/docs/pillars.rst @@ -4,9 +4,9 @@ Pillars Declare Once ------------ -Crew definitions should be portable across supported runtimes. Framework +Fabric agent definitions should be portable across supported runtimes. Framework specificity belongs in adapters and optional extras, not in the basic -crew schema. +fabric agent schema. Data And Context Move Together ------------------------------ diff --git a/docs/vendor-fabric.rst b/docs/vendor-fabric.rst index 7207b1a..1ad2377 100644 --- a/docs/vendor-fabric.rst +++ b/docs/vendor-fabric.rst @@ -8,7 +8,7 @@ package still imports without vendor SDKs installed. The integration rule is simple: vendor IO, provider capability metadata, and the SecretSync Python facade/capability surface belong in ``vendor-fabric``. The SecretSync Go runtime and gopy binding source -belong in ``secrets-sync``. Crew orchestration, runtime selection, and +belong in ``secrets-sync``. Fabric agent orchestration, runtime selection, and agent-facing framework wrappers belong here. ``AgenticData`` subclasses ``VendorData`` when ``vendor-fabric`` is @@ -43,3 +43,10 @@ also expose a provider catalog from the superclass: capability route into a lazy ``VendorCapabilityTool``. The provider connectors, install availability, and dispatch behavior still come from ``vendor-fabric``. + +MCP ownership follows the same boundary. ``agentic-fabric`` owns the +``agentic-fabric-vendor-mcp`` and ``agentic-fabric-meshy-mcp`` adapter entry +points because MCP is an agent runtime transport. The underlying connector +classes, provider methods, Meshy capability metadata, credentials, and network +calls remain in ``vendor-fabric`` and are imported lazily only when the MCP +server is created. diff --git a/packages/agentic-fabric/CHANGELOG.md b/packages/agentic-fabric/CHANGELOG.md index acbe9b7..3e40bc1 100644 --- a/packages/agentic-fabric/CHANGELOG.md +++ b/packages/agentic-fabric/CHANGELOG.md @@ -6,14 +6,15 @@ ### Initial Stable Release -Framework-agnostic AI crew orchestration is now production-ready! +Framework-agnostic AI fabric agent orchestration is now production-ready! ### Features -- **Multi-Framework Support**: Run crews on CrewAI, LangGraph, or Strands +- **Multi-Framework Support**: Run fabric agents on CrewAI, LangGraph, or Strands - **Auto-Detection**: Automatically selects best available framework - **Universal YAML Format**: Define once, run anywhere - **Single-Agent CLI Runners**: Support for aider, claude-code, ollama, and more +- **MCP Adapters**: Expose vendor-fabric catalog/data methods and Meshy capability metadata over MCP - **Knowledge Base Integration**: Load domain knowledge from markdown files - **Agent Archetypes**: Reusable agent templates - **Comprehensive Testing**: 122+ unit tests, E2E test suite diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index a2c8381..1a259cc 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -5,9 +5,9 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Python](https://img.shields.io/pypi/pyversions/agentic-fabric.svg)](https://pypi.org/project/agentic-fabric/) -Framework-agnostic agent crew orchestration. Declare crews once in YAML, then +Framework-agnostic agent fabric orchestration. Declare fabric agents once in YAML, then run them on CrewAI, LangGraph, Strands, or local CLI runners without changing a -single crew definition. Runtime frameworks are optional and are detected lazily +single fabric agent definition. Runtime frameworks are optional and are detected lazily from what is installed. [Documentation](https://jonbogaty.com/agentic-fabric/) | [Source](https://github.com/jbcom/agentic-fabric) | [Issues](https://github.com/jbcom/agentic-fabric/issues) @@ -44,10 +44,10 @@ installs are unaffected. ## Quick Start -### 1. Define a Crew (YAML) +### 1. Define a Fabric Agent (YAML) ```yaml -# .crewai/crews/analyzer/agents.yaml +# .crewai/fabric_agents/analyzer/agents.yaml code_reviewer: role: Senior Code Reviewer goal: Find bugs and improvements @@ -55,7 +55,7 @@ code_reviewer: ``` ```yaml -# .crewai/crews/analyzer/tasks.yaml +# .crewai/fabric_agents/analyzer/tasks.yaml review_code: description: Review the provided code for issues expected_output: List of findings with severity @@ -67,16 +67,16 @@ review_code: ```python from pathlib import Path -from agentic_fabric import detect_framework, get_crew_config, run_crew_auto +from agentic_fabric import detect_framework, get_fabric_agent_config, run_fabric_agent_auto # See what framework is available framework = detect_framework() -# Load a crew manifest discovered in a package or workspace -config = get_crew_config(Path(".crew"), "analyzer") +# Load a fabric agent manifest discovered in a package or workspace +config = get_fabric_agent_config(Path(".fabric"), "analyzer") # Auto-detect best framework and run -result = run_crew_auto(config, inputs={"code": "..."}) +result = run_fabric_agent_auto(config, inputs={"code": "..."}) ``` Or from the CLI: @@ -91,8 +91,8 @@ agentic-fabric run my-package analyzer --input "Review this code: ..." from agentic_fabric import get_runner runner = get_runner("langgraph") # Force LangGraph -crew = runner.build_crew(config) -result = runner.run(crew, inputs) +fabric_agent = runner.build_fabric_agent(config) +result = runner.run(fabric_agent, inputs) ``` ### 4. Carry Runtime Context with Data @@ -103,13 +103,13 @@ from agentic_fabric import AgenticData, get_framework_info print(get_framework_info()) session = AgenticData({"repo": "jbcom/agentic-fabric"}) -session.register_agent("reviewer", config) -result = session.run_agent("reviewer", runtime="crewai") +session.register_fabric_agent("reviewer", config) +result = session.run_fabric_agent("reviewer", runtime="crewai") ``` ## Key Features -- Framework agnostic: one crew definition, multiple runtime backends. +- Framework agnostic: one fabric agent definition, multiple runtime backends. - Lazy imports: core package import does not require CrewAI, LangGraph, Strands, or vendor SDKs. - Framework-neutral file tools: built-in filesystem tools can be resolved @@ -117,7 +117,7 @@ result = session.run_agent("reviewer", runtime="crewai") only when their optional dependencies are present. - Focused extras: `langgraph`, `strands`, `mcp`, `scraping`, `tests`, `typing`, `docs`, and `dev`. -- `AgenticData`: carries data, registered crews, active runtime selection, and +- `AgenticData`: carries data, registered fabric agents, active runtime selection, and vendor-layer context together. - Capability decorators: runners and tools expose declared capabilities through read-only metadata and deterministic dispatch. @@ -126,9 +126,9 @@ result = session.run_agent("reviewer", runtime="crewai") - Vendor tool catalogs: `AgenticData.vendor_tools()` adapts inherited `VendorData` capability metadata into agent-facing tools without importing provider SDKs directly. -- YAML-first: crew configuration in YAML, not Python boilerplate. -- Hierarchical orchestration: `ManagerAgent` delegates across crews. -- Package discovery: finds `.crew/`, `.crewai/`, `.langgraph/`, and +- YAML-first: fabric agent configuration in YAML, not Python boilerplate. +- Hierarchical orchestration: `ManagerAgent` delegates across fabric agents. +- Package discovery: finds `.fabric/`, `.crewai/`, `.langgraph/`, and `.strands/` directories. - Vendor passthrough extras are deferred until `vendor-fabric` is published with a stable optional-extra contract. @@ -145,7 +145,7 @@ You can always force a specific runner with `get_runner("langgraph")` or If the selected runtime is not installed, errors point to the matching `agentic-fabric[...]` extra. Framework-specific config directories also enforce -their runtime: a crew in `.langgraph/` will not silently run on CrewAI. +their runtime: a fabric agent in `.langgraph/` will not silently run on CrewAI. ## Local CLI Runners @@ -161,6 +161,22 @@ Profiles are loaded from the packaged `local_cli_profiles.yaml`, validated before use, and rejected on POSIX systems if the profiles file is group- or world-writable. +## MCP Adapters + +The `mcp` extra installs the MCP transport dependency and enables two console +entry points: + +```bash +agentic-fabric-vendor-mcp +agentic-fabric-meshy-mcp +``` + +`agentic-fabric-vendor-mcp` exposes credential-free vendor catalog tools and +public `vendor-fabric` data methods. `agentic-fabric-meshy-mcp` converts +Meshy capability metadata from `vendor-fabric[meshy]` into MCP tools. Both +servers import provider code lazily; install the matching `vendor-fabric` +package/extras in the same environment before running provider-backed tools. + ## Repository Boundary - `extended-data` owns base data containers, logging, input handling, files, @@ -168,7 +184,7 @@ world-writable. - `vendor-fabric` owns vendor connectors, provider-backed sync, the SecretSync Python facade/capability surfaces, provider capability metadata, and provider dispatch. -- `agentic-fabric` owns crew discovery, runner selection, framework adapters, +- `agentic-fabric` owns fabric agent discovery, runner selection, framework adapters, agent-facing tool wrappers, and orchestration. Full guides and API documentation are published at diff --git a/packages/agentic-fabric/examples/README.md b/packages/agentic-fabric/examples/README.md index 4453859..b2f0002 100644 --- a/packages/agentic-fabric/examples/README.md +++ b/packages/agentic-fabric/examples/README.md @@ -11,4 +11,4 @@ python examples/runtime_context.py ``` The bundled `sample_workspace/` directory demonstrates a framework-agnostic -`.crew/` layout. +`.fabric/` layout. diff --git a/packages/agentic-fabric/examples/discovery_workflow.py b/packages/agentic-fabric/examples/discovery_workflow.py index 264c2a3..eaae24a 100644 --- a/packages/agentic-fabric/examples/discovery_workflow.py +++ b/packages/agentic-fabric/examples/discovery_workflow.py @@ -1,4 +1,4 @@ -"""Discover and inspect a framework-agnostic crew workspace.""" +"""Discover and inspect a framework-agnostic fabric workspace.""" from __future__ import annotations @@ -8,32 +8,32 @@ from pathlib import Path from typing import Any -from agentic_fabric.core.discovery import discover_packages, get_crew_config, load_manifest +from agentic_fabric.core.discovery import discover_packages, get_fabric_agent_config, load_manifest DEFAULT_WORKSPACE = Path(__file__).parent / "sample_workspace" def summarize_workspace(workspace_root: Path = DEFAULT_WORKSPACE) -> dict[str, Any]: - """Return a deterministic summary of crews in a workspace.""" + """Return a deterministic summary of fabric agents in a workspace.""" packages = discover_packages(workspace_root=workspace_root) summary: dict[str, Any] = {"workspace": str(workspace_root), "packages": {}} for package_name, config_dir in sorted(packages.items()): manifest = load_manifest(config_dir) - crew_summaries = {} - for crew_name in sorted(manifest.get("crews", {})): - crew_config = get_crew_config(config_dir, crew_name) - crew_summaries[crew_name] = { - "description": crew_config.get("description", ""), - "required_framework": crew_config.get("required_framework"), - "agents": sorted(crew_config.get("agents", {})), - "tasks": sorted(crew_config.get("tasks", {})), + fabric_agent_summaries = {} + for fabric_agent_name in sorted(manifest.get("fabric_agents", {})): + fabric_agent_config = get_fabric_agent_config(config_dir, fabric_agent_name) + fabric_agent_summaries[fabric_agent_name] = { + "description": fabric_agent_config.get("description", ""), + "required_framework": fabric_agent_config.get("required_framework"), + "agents": sorted(fabric_agent_config.get("agents", {})), + "tasks": sorted(fabric_agent_config.get("tasks", {})), } summary["packages"][package_name] = { "config_dir": config_dir.name, - "crews": crew_summaries, + "fabric_agents": fabric_agent_summaries, } return summary diff --git a/packages/agentic-fabric/examples/runtime_context.py b/packages/agentic-fabric/examples/runtime_context.py index ac53ae3..1b4b85a 100644 --- a/packages/agentic-fabric/examples/runtime_context.py +++ b/packages/agentic-fabric/examples/runtime_context.py @@ -12,12 +12,12 @@ def inspect_runtime_context() -> dict[str, Any]: """Return a serializable summary of runtime context behavior.""" data = AgenticData({"task": "review"}) - data.register_agent("reviewer", {"name": "reviewer", "agents": {}, "tasks": {}}) + data.register_fabric_agent("reviewer", {"name": "reviewer", "agents": {}, "tasks": {}}) data.use_runtime("crewai", strict=False) return { "active_runtime": data.active_runtime, - "registered_agents": sorted(data.agent_registry), + "registered_fabric_agents": sorted(data.fabric_agents), "runtime_names": [item["name"] for item in get_framework_info()], "vendor_fabric_available": data.vendor_fabric_available, } diff --git a/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/manifest.yaml b/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/manifest.yaml deleted file mode 100644 index a682c87..0000000 --- a/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/manifest.yaml +++ /dev/null @@ -1,7 +0,0 @@ -name: review -description: Framework-agnostic review crew example. -crews: - implementation_review: - description: Review an implementation for correctness and migration readiness. - agents: crews/implementation_review/agents.yaml - tasks: crews/implementation_review/tasks.yaml diff --git a/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/crews/implementation_review/agents.yaml b/packages/agentic-fabric/examples/sample_workspace/packages/review/.fabric/fabric_agents/implementation_review/agents.yaml similarity index 100% rename from packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/crews/implementation_review/agents.yaml rename to packages/agentic-fabric/examples/sample_workspace/packages/review/.fabric/fabric_agents/implementation_review/agents.yaml diff --git a/packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/crews/implementation_review/tasks.yaml b/packages/agentic-fabric/examples/sample_workspace/packages/review/.fabric/fabric_agents/implementation_review/tasks.yaml similarity index 100% rename from packages/agentic-fabric/examples/sample_workspace/packages/review/.crew/crews/implementation_review/tasks.yaml rename to packages/agentic-fabric/examples/sample_workspace/packages/review/.fabric/fabric_agents/implementation_review/tasks.yaml diff --git a/packages/agentic-fabric/examples/sample_workspace/packages/review/.fabric/manifest.yaml b/packages/agentic-fabric/examples/sample_workspace/packages/review/.fabric/manifest.yaml new file mode 100644 index 0000000..6ae11d3 --- /dev/null +++ b/packages/agentic-fabric/examples/sample_workspace/packages/review/.fabric/manifest.yaml @@ -0,0 +1,7 @@ +name: review +description: Framework-agnostic review fabric_agent example. +fabric_agents: + implementation_review: + description: Review an implementation for correctness and migration readiness. + agents: fabric_agents/implementation_review/agents.yaml + tasks: fabric_agents/implementation_review/tasks.yaml diff --git a/packages/agentic-fabric/pyproject.toml b/packages/agentic-fabric/pyproject.toml index 45f0a26..30cd9c9 100644 --- a/packages/agentic-fabric/pyproject.toml +++ b/packages/agentic-fabric/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "agentic-fabric" version = "1.0.0" -description = "Framework-agnostic agent crew orchestration for CrewAI, LangGraph, Strands, local CLI runners, and vendor-fabric tools" +description = "Framework-agnostic agent fabric orchestration for CrewAI, LangGraph, Strands, local CLI runners, and vendor-fabric tools" requires-python = ">=3.11" license = { text = "MIT" } readme = "README.md" @@ -60,6 +60,8 @@ docs = [ [project.scripts] agentic-fabric = "agentic_fabric.main:main" +agentic-fabric-meshy-mcp = "agentic_fabric.tools.meshy_mcp:main" +agentic-fabric-vendor-mcp = "agentic_fabric.tools.vendor_mcp:main" [project.urls] Documentation = "https://jonbogaty.com/agentic-fabric/" @@ -75,6 +77,7 @@ addopts = ["-ra", "--strict-markers", "--strict-config"] testpaths = ["tests"] xfail_strict = true asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" timeout = 30 timeout_method = "thread" markers = [ @@ -174,7 +177,6 @@ module = [ "langchain_anthropic", "langgraph", "langgraph.*", - "mesh_toolkit.*", "strands", ] ignore_missing_imports = true diff --git a/packages/agentic-fabric/src/agentic_fabric/__init__.py b/packages/agentic-fabric/src/agentic_fabric/__init__.py index f8ab67a..d0a9a5b 100644 --- a/packages/agentic-fabric/src/agentic_fabric/__init__.py +++ b/packages/agentic-fabric/src/agentic_fabric/__init__.py @@ -1,28 +1,28 @@ -"""agentic-fabric: Framework-agnostic AI crew orchestration. +"""agentic-fabric: Framework-agnostic AI fabric orchestration. -Declare crews once, run on CrewAI, LangGraph, or Strands. +Declare fabric agents once, run on CrewAI, LangGraph, or Strands. Usage: - from agentic_fabric.core.decomposer import run_crew_auto, get_runner, detect_framework - from agentic_fabric.core.discovery import discover_packages, get_crew_config + from agentic_fabric.core.decomposer import run_fabric_agent_auto, get_runner, detect_framework + from agentic_fabric.core.discovery import discover_packages, get_fabric_agent_config from agentic_fabric.core.manager import ManagerAgent - # Auto-detect framework and run a crew + # Auto-detect framework and run a fabric agent packages = discover_packages() - config = get_crew_config(packages["my-package"], "my_crew") - result = run_crew_auto(config, inputs={"task": "..."}) + config = get_fabric_agent_config(packages["my-package"], "reviewer") + result = run_fabric_agent_auto(config, inputs={"task": "..."}) # Or get a specific runner runner = get_runner("crewai") # or "langgraph", "strands" - crew = runner.build_crew(config) - result = runner.run(crew, inputs) + fabric_agent = runner.build_fabric_agent(config) + result = runner.run(fabric_agent, inputs) # Or use a hierarchical manager agent class MyManager(ManagerAgent): def __init__(self): - super().__init__(crews={ - "design": "design_crew", - "implementation": "impl_crew" + super().__init__(fabric_agents={ + "design": "design_review", + "implementation": "implementation_review" }) async def execute_workflow(self, task): @@ -50,19 +50,19 @@ async def execute_workflow(self, task): tool_capability, ) from agentic_fabric.core.decomposer import ( - decompose_crew, + compose_fabric_agent, detect_framework, get_available_frameworks, get_framework_info, get_runner, is_framework_available, - run_crew_auto, + run_fabric_agent_auto, ) from agentic_fabric.core.discovery import ( discover_all_framework_configs, discover_packages, - get_crew_config, - list_crews, + get_fabric_agent_config, + list_fabric_agents, ) from agentic_fabric.core.manager import ManagerAgent @@ -74,17 +74,17 @@ async def execute_workflow(self, task): "ManagerAgent", "__version__", "agent_capability", - "decompose_crew", + "compose_fabric_agent", "detect_framework", "discover_all_framework_configs", "discover_packages", "get_available_frameworks", - "get_crew_config", + "get_fabric_agent_config", "get_framework_info", "get_runner", "is_framework_available", - "list_crews", - "run_crew_auto", + "list_fabric_agents", + "run_fabric_agent_auto", "runtime_capability", "tool_capability", ] diff --git a/packages/agentic-fabric/src/agentic_fabric/agentic_data.py b/packages/agentic-fabric/src/agentic_fabric/agentic_data.py index 1126a96..c76c658 100644 --- a/packages/agentic-fabric/src/agentic_fabric/agentic_data.py +++ b/packages/agentic-fabric/src/agentic_fabric/agentic_data.py @@ -61,7 +61,7 @@ def call(self, operation: str, *_: Any, **__: Any) -> Any: class AgenticData(_VendorDataBase): - """VendorData extension with active runtime and agent registry context.""" + """VendorData extension with active runtime and fabric agent registry context.""" runtime_priority: ClassVar[tuple[str, ...]] = tuple(runtime_names()) @@ -70,24 +70,24 @@ def __init__( value: Any = None, *, fabric: Any | None = None, - agent_registry: Mapping[str, Mapping[str, Any]] | None = None, + fabric_agents: Mapping[str, Mapping[str, Any]] | None = None, logger: Any | None = None, active_runtime: str | None = None, **fabric_kwargs: Any, ) -> None: - """Initialize data, registered crews, and optional active runtime.""" + """Initialize data, registered fabric agents, and optional active runtime.""" super().__init__(value, fabric=fabric, logger=logger, **fabric_kwargs) - self._agent_registry: dict[str, dict[str, Any]] = { - name: dict(config) for name, config in (agent_registry or {}).items() + self._fabric_agents: dict[str, dict[str, Any]] = { + name: dict(config) for name, config in (fabric_agents or {}).items() } self._active_runtime: str | None = None if active_runtime is not None: self.use_runtime(active_runtime, strict=False) @property - def agent_registry(self) -> Mapping[str, Mapping[str, Any]]: - """Return registered agent/crew definitions.""" - return MappingProxyType(self._agent_registry) + def fabric_agents(self) -> Mapping[str, Mapping[str, Any]]: + """Return registered fabric agent definitions.""" + return MappingProxyType(self._fabric_agents) @property def active_runtime(self) -> str | None: @@ -104,14 +104,14 @@ def cast(self, value: Any) -> AgenticData: super().cast(value) return self - def register_agent(self, name: str, crew_config: Mapping[str, Any]) -> AgenticData: - """Register a named crew config for ``run_agent`` lookup.""" - self._agent_registry[name] = dict(crew_config) + def register_fabric_agent(self, name: str, fabric_agent_config: Mapping[str, Any]) -> AgenticData: + """Register a named fabric agent config for ``run_fabric_agent`` lookup.""" + self._fabric_agents[name] = dict(fabric_agent_config) return self - def unregister_agent(self, name: str) -> AgenticData: - """Remove a named crew config if it exists.""" - self._agent_registry.pop(name, None) + def unregister_fabric_agent(self, name: str) -> AgenticData: + """Remove a named fabric agent config if it exists.""" + self._fabric_agents.pop(name, None) return self def use_runtime(self, runtime: str, *, strict: bool = True) -> AgenticData: @@ -148,15 +148,15 @@ def select_runtime( self, runtime: str | None = None, *, - crew_config: Mapping[str, Any] | None = None, + fabric_agent_config: Mapping[str, Any] | None = None, ) -> str: """Select a runtime using explicit, active, manifest, then auto priority.""" - required_runtime = _manifest_runtime(crew_config) + required_runtime = _manifest_runtime(fabric_agent_config) requested = _normalize_runtime(runtime) active = _normalize_runtime(self._active_runtime) if requested is not None and required_runtime is not None and requested != required_runtime: - msg = f"Crew requires {required_runtime} but {requested} was requested" + msg = f"Fabric agent requires {required_runtime} but {requested} was requested" raise ValueError(msg) if requested is not None: @@ -164,7 +164,7 @@ def select_runtime( if active is not None: if required_runtime is not None and active != required_runtime: - msg = f"Active runtime {active} conflicts with crew requirement {required_runtime}" + msg = f"Active runtime {active} conflicts with fabric agent requirement {required_runtime}" raise ValueError(msg) return _require_available(active) @@ -175,32 +175,22 @@ def select_runtime( return detect_framework() - def run_crew( + def run_fabric_agent( self, - crew_config: Mapping[str, Any], - inputs: Mapping[str, Any] | None = None, - *, - runtime: str | None = None, - ) -> str: - """Run one crew config through the selected runtime.""" - from agentic_fabric.core.decomposer import run_crew_auto - - selected_runtime = self.select_runtime(runtime, crew_config=crew_config) - return run_crew_auto(dict(crew_config), inputs=dict(inputs or {}), framework=selected_runtime) - - def run_agent( - self, - agent: str | Mapping[str, Any], + fabric_agent: str | Mapping[str, Any], inputs: Mapping[str, Any] | None = None, *, runtime: str | None = None, **input_kwargs: Any, ) -> str: - """Run a registered agent/crew by name or a direct crew config.""" - crew_config = self._lookup_agent(agent) + """Run a registered fabric agent by name or a direct fabric agent config.""" + from agentic_fabric.core.decomposer import run_fabric_agent_auto + + fabric_agent_config = self._lookup_fabric_agent(fabric_agent) merged_inputs = dict(inputs or {}) merged_inputs.update(input_kwargs) - return self.run_crew(crew_config, merged_inputs, runtime=runtime) + selected_runtime = self.select_runtime(runtime, fabric_agent_config=fabric_agent_config) + return run_fabric_agent_auto(fabric_agent_config, inputs=merged_inputs, framework=selected_runtime) def call_runtime(self, capability: str, *args: Any, runtime: str | None = None, **kwargs: Any) -> Any: """Call a declared capability on a selected runtime runner.""" @@ -222,11 +212,11 @@ def vendor_tools( return vendor_capability_tools(self, provider=provider, include_unavailable=include_unavailable) def __getattr__(self, name: str) -> Any: - """Expose ``run_`` helpers while preserving VendorData dispatch.""" + """Expose ``run_`` helpers while preserving VendorData dispatch.""" if name.startswith("run_"): - agent_name = name.removeprefix("run_") - if agent_name in self._agent_registry: - return lambda *args, **kwargs: self.run_agent(agent_name, *args, **kwargs) + fabric_agent_name = name.removeprefix("run_") + if fabric_agent_name in self._fabric_agents: + return lambda *args, **kwargs: self.run_fabric_agent(fabric_agent_name, *args, **kwargs) try: return super().__getattr__(name) # type: ignore[misc] @@ -234,19 +224,19 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(f"{type(self).__name__!s} has no attribute {name!r}") from None def __dir__(self) -> list[str]: - """Include dynamic registered-agent helpers in introspection.""" - dynamic = [f"run_{name}" for name in self._agent_registry] + """Include dynamic registered-fabric-agent helpers in introspection.""" + dynamic = [f"run_{name}" for name in self._fabric_agents] return sorted({*super().__dir__(), *dynamic}) - def _lookup_agent(self, agent: str | Mapping[str, Any]) -> dict[str, Any]: - """Return a crew config from a name or direct mapping.""" - if isinstance(agent, Mapping): - return dict(agent) + def _lookup_fabric_agent(self, fabric_agent: str | Mapping[str, Any]) -> dict[str, Any]: + """Return a fabric agent config from a name or direct mapping.""" + if isinstance(fabric_agent, Mapping): + return dict(fabric_agent) try: - return dict(self._agent_registry[agent]) + return dict(self._fabric_agents[fabric_agent]) except KeyError as exc: - options = ", ".join(sorted(self._agent_registry)) or "none registered" - msg = f"Unknown agent '{agent}'. Registered agents: {options}" + options = ", ".join(sorted(self._fabric_agents)) or "none registered" + msg = f"Unknown fabric agent '{fabric_agent}'. Registered fabric agents: {options}" raise KeyError(msg) from exc @@ -258,11 +248,11 @@ def _normalize_runtime(runtime: str | None) -> str | None: return None if normalized == "auto" else normalized -def _manifest_runtime(crew_config: Mapping[str, Any] | None) -> str | None: - """Read runtime requirements from a crew manifest/config mapping.""" - if crew_config is None: +def _manifest_runtime(fabric_agent_config: Mapping[str, Any] | None) -> str | None: + """Read runtime requirements from a fabric agent manifest/config mapping.""" + if fabric_agent_config is None: return None - runtime = crew_config.get("required_framework") or crew_config.get("runtime") or crew_config.get("framework") + runtime = fabric_agent_config.get("required_framework") or fabric_agent_config.get("runtime") or fabric_agent_config.get("framework") return _normalize_runtime(str(runtime)) if runtime else None diff --git a/packages/agentic-fabric/src/agentic_fabric/base/archetypes.yaml b/packages/agentic-fabric/src/agentic_fabric/base/archetypes.yaml index ac49b7e..5a114c2 100644 --- a/packages/agentic-fabric/src/agentic_fabric/base/archetypes.yaml +++ b/packages/agentic-fabric/src/agentic_fabric/base/archetypes.yaml @@ -3,7 +3,7 @@ # These are base templates that packages can extend in their own agents.yaml. # Use the 'extends' field to inherit from an archetype. # -# Example usage in packages/mypackage/.crewai/crews/builder/agents.yaml: +# Example usage in packages/mypackage/.crewai/fabric_agents/builder/agents.yaml: # my_engineer: # extends: senior_engineer # variables: diff --git a/packages/agentic-fabric/src/agentic_fabric/core/__init__.py b/packages/agentic-fabric/src/agentic_fabric/core/__init__.py index 7449738..034dec0 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/__init__.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/__init__.py @@ -1,18 +1,18 @@ -"""Core CrewAI engine - discovery, loading, and running of package-defined crews.""" +"""Core fabric engine for package discovery, loading, and execution.""" from __future__ import annotations -from agentic_fabric.core.discovery import discover_packages, get_crew_config, load_manifest -from agentic_fabric.core.loader import load_crew_from_config +from agentic_fabric.core.discovery import discover_packages, get_fabric_agent_config, load_manifest +from agentic_fabric.core.loader import load_fabric_agent_from_config from agentic_fabric.core.manager import ManagerAgent -from agentic_fabric.core.runner import run_crew +from agentic_fabric.core.runner import run_fabric_agent __all__ = [ "ManagerAgent", "discover_packages", - "get_crew_config", - "load_crew_from_config", + "get_fabric_agent_config", + "load_fabric_agent_from_config", "load_manifest", - "run_crew", + "run_fabric_agent", ] diff --git a/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py b/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py index 6c4a7a0..a86cf2a 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/decomposer.py @@ -1,6 +1,6 @@ """Framework decomposition - auto-detect and select AI framework. -This module provides the core capability of agentic-fabric: declaring crews +This module provides the core capability of agentic-fabric: declaring fabric agents once and running them on CrewAI, LangGraph, or Strands depending on what's installed. It also supports single-agent CLI runners for simpler tasks. @@ -199,48 +199,48 @@ def is_cli_runner_available(profile: str) -> bool: return False -def _resolve_required_framework(crew_config: dict[str, Any], framework: str | None = None) -> str | None: - """Return the framework selected after honoring crew-level requirements.""" - required_framework = crew_config.get("required_framework") +def _resolve_required_framework(fabric_agent_config: dict[str, Any], framework: str | None = None) -> str | None: + """Return the framework selected after honoring fabric-agent-level requirements.""" + required_framework = fabric_agent_config.get("required_framework") if not required_framework: return framework if framework and framework not in (required_framework, "auto"): raise ValueError( - f"Crew requires {required_framework} (defined in .{required_framework}/ directory) " + f"Fabric agent requires {required_framework} (defined in .{required_framework}/ directory) " f"but {framework} was requested" ) if not is_framework_available(required_framework): raise RuntimeError( - f"Crew requires {required_framework} but it's not installed. " + f"Fabric agent requires {required_framework} but it's not installed. " f"Install with: {_get_install_command(required_framework)}" ) return required_framework -def decompose_crew( - crew_config: dict[str, Any], +def compose_fabric_agent( + fabric_agent_config: dict[str, Any], framework: str | None = None, ) -> Any: - """Decompose a crew configuration to a framework-specific crew. + """Compose a fabric agent configuration into a runtime-specific object. - This is the core function that converts a framework-agnostic crew - definition into a runnable crew for the target framework. + This is the core function that converts a framework-agnostic fabric agent + definition into a runnable fabric agent for the target framework. Args: - crew_config: Crew configuration from loader. + fabric_agent_config: Fabric agent configuration from loader. framework: Target framework or None for auto-detect. - If crew_config has required_framework, that takes precedence. + If fabric_agent_config has required_framework, that takes precedence. Returns: - Framework-specific crew object ready to run. + Runtime-specific fabric agent object ready to run. Raises: RuntimeError: If required framework is not available. """ - runner = get_runner(_resolve_required_framework(crew_config, framework)) - return runner.build_crew(crew_config) + runner = get_runner(_resolve_required_framework(fabric_agent_config, framework)) + return runner.build_fabric_agent(fabric_agent_config) def _get_install_command(framework: str) -> str: @@ -252,27 +252,27 @@ def _get_install_command(framework: str) -> str: # Convenience function for simple use cases -def run_crew_auto( - crew_config: dict[str, Any], +def run_fabric_agent_auto( + fabric_agent_config: dict[str, Any], inputs: dict[str, Any] | None = None, framework: str | None = None, ) -> str: - """Run a crew using the best available framework. + """Run a fabric agent using the best available framework. Args: - crew_config: Crew configuration from loader. - inputs: Optional inputs for the crew. - framework: Optional framework override. If crew_config has + fabric_agent_config: Fabric agent configuration from loader. + inputs: Optional inputs for the fabric agent. + framework: Optional framework override. If fabric_agent_config has required_framework (from .crewai/.strands/.langgraph dir), that takes precedence. Returns: - Crew output as string. + Fabric agent output as string. Raises: RuntimeError: If required framework is not available. ValueError: If requested framework conflicts with required framework. """ - runner = get_runner(_resolve_required_framework(crew_config, framework)) - crew = runner.build_crew(crew_config) - return runner.run(crew, inputs or {}) + runner = get_runner(_resolve_required_framework(fabric_agent_config, framework)) + fabric_agent = runner.build_fabric_agent(fabric_agent_config) + return runner.run(fabric_agent, inputs or {}) diff --git a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py index e4466dc..89a101f 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py @@ -1,6 +1,7 @@ -"""Discovery module - finds packages with crew configuration directories. +"""Discovery module - finds packages with fabric agent configuration directories. Supports framework-specific configuration directories: +- .fabric/ - framework-agnostic fabric configuration - .crewai/ - CrewAI-specific configurations (default) - .langgraph/ - LangGraph-specific configurations - .strands/ - Strands-specific configurations @@ -21,23 +22,23 @@ logger = logging.getLogger(__name__) # Framework directory names in priority order -# .crew is framework-agnostic (can run on any available framework) +# .fabric is framework-agnostic (can run on any available framework) # Framework-specific dirs enforce that framework -FRAMEWORK_DIRS = [".crew", ".crewai", ".langgraph", ".strands"] +FRAMEWORK_DIRS = [".fabric", ".crewai", ".langgraph", ".strands"] # Mapping from directory name to framework name -# .crew maps to None (framework-agnostic, auto-detect at runtime) +# .fabric maps to None (framework-agnostic, auto-detect at runtime) DIR_TO_FRAMEWORK: dict[str, str | None] = { - ".crew": None, # Framework-agnostic + ".fabric": None, # Framework-agnostic ".crewai": "crewai", ".langgraph": "langgraph", ".strands": "strands", } # Mapping from framework name to directory name -# None (agnostic) maps to .crew +# None (agnostic) maps to .fabric FRAMEWORK_TO_DIR: dict[str | None, str] = { - None: ".crew", + None: ".fabric", "crewai": ".crewai", "langgraph": ".langgraph", "strands": ".strands", @@ -61,7 +62,7 @@ def discover_packages( workspace_root: Path | None = None, framework: str | None = None, ) -> dict[str, Path]: - """Discover all packages with crew configuration directories. + """Discover all packages with fabric agent configuration directories. Args: workspace_root: Root of the workspace. If None, auto-detected. @@ -123,7 +124,7 @@ def discover_all_framework_configs( Returns: Dict mapping package name to dict of framework -> config_dir. Example: {"sample": {"crewai": Path(...), "strands": Path(...)}} - Framework can be None for framework-agnostic .crew/ directories. + Framework can be None for framework-agnostic .fabric/ directories. """ if workspace_root is None: workspace_root = get_workspace_root() @@ -163,16 +164,16 @@ def discover_all_framework_configs( return packages -def load_manifest(crewai_dir: Path) -> dict[str, Any]: - """Load a package's CrewAI manifest. +def load_manifest(config_dir: Path) -> dict[str, Any]: + """Load a package's fabric manifest. Args: - crewai_dir: Path to the .crewai/ directory. + config_dir: Path to the fabric or runtime-specific config directory. Returns: Parsed manifest as a dictionary. """ - manifest_path = crewai_dir / "manifest.yaml" + manifest_path = config_dir / "manifest.yaml" with open(manifest_path, encoding="utf-8") as f: result = yaml.safe_load(f) return result or {} @@ -206,12 +207,12 @@ def get_framework_from_config_dir(config_dir: Path) -> str | None: return DIR_TO_FRAMEWORK.get(dir_name) -def get_crew_config(config_dir: Path, crew_name: str) -> dict: - """Load a specific crew's configuration. +def get_fabric_agent_config(config_dir: Path, fabric_agent_name: str) -> dict: + """Load a specific fabric agent's configuration. Args: - config_dir: Path to the config directory (.crewai/, .strands/, .langgraph/). - crew_name: Name of the crew to load. + config_dir: Path to the config directory (.fabric/, .crewai/, .strands/, .langgraph/). + fabric_agent_name: Name of the fabric agent to load. Returns: Dict with agents, tasks, knowledge_paths, and required_framework. @@ -219,21 +220,21 @@ def get_crew_config(config_dir: Path, crew_name: str) -> dict: if the config is in a framework-specific directory. Raises: - ValueError: If crew not found in manifest. + ValueError: If fabric agent not found in manifest. """ manifest = load_manifest(config_dir) - crews = manifest.get("crews", {}) - crew_config = crews.get(crew_name) + fabric_agents = manifest.get("fabric_agents", {}) + fabric_agent_config = fabric_agents.get(fabric_agent_name) - if not crew_config: - available = list(crews.keys()) - raise ValueError(f"Crew '{crew_name}' not found. Available: {available}") + if not fabric_agent_config: + available = list(fabric_agents.keys()) + raise ValueError(f"Fabric agent '{fabric_agent_name}' not found. Available: {available}") - agents_rel = crew_config.get("agents") - tasks_rel = crew_config.get("tasks") + agents_rel = fabric_agent_config.get("agents") + tasks_rel = fabric_agent_config.get("tasks") if not agents_rel or not tasks_rel: missing = "agents" if not agents_rel else "tasks" - msg = f"Crew '{crew_name}' is missing required key: {missing}" + msg = f"Fabric agent '{fabric_agent_name}' is missing required key: {missing}" raise ValueError(msg) # Load agents and tasks YAML @@ -245,7 +246,7 @@ def get_crew_config(config_dir: Path, crew_name: str) -> dict: # Resolve knowledge paths knowledge_paths = [] - for kp in crew_config.get("knowledge", []): + for kp in fabric_agent_config.get("knowledge", []): full_path = _resolve_config_path(config_dir, kp) if full_path.is_dir(): knowledge_paths.append(full_path) @@ -255,7 +256,7 @@ def get_crew_config(config_dir: Path, crew_name: str) -> dict: required_framework = get_framework_from_config_dir(config_dir) # Also check manifest-level preferred_framework (can be overridden by dir) - manifest_framework = crew_config.get("preferred_framework") + manifest_framework = fabric_agent_config.get("preferred_framework") if ( manifest_framework and manifest_framework != "auto" @@ -264,16 +265,16 @@ def get_crew_config(config_dir: Path, crew_name: str) -> dict: ): # Manifest can specify preference, but directory takes precedence logger.warning( - f"Warning: Crew '{crew_name}' specifies preferred_framework={manifest_framework} " + f"Warning: fabric agent '{fabric_agent_name}' specifies preferred_framework={manifest_framework} " f"but is in {config_dir.name}/ directory which requires {required_framework}" ) # Get LLM config from manifest - llm_config = manifest.get("llm", crew_config.get("llm", {})) + llm_config = manifest.get("llm", fabric_agent_config.get("llm", {})) return { - "name": crew_name, - "description": crew_config.get("description", ""), + "name": fabric_agent_name, + "description": fabric_agent_config.get("description", ""), "agents": agents, "tasks": tasks, "knowledge_paths": knowledge_paths, @@ -287,21 +288,21 @@ def get_crew_config(config_dir: Path, crew_name: str) -> dict: } -def list_crews( +def list_fabric_agents( package_name: str | None = None, framework: str | None = None, ) -> dict[str, list[dict]]: - """List all available crews, optionally filtered by package or framework. + """List all available fabric agents, optionally filtered by package or framework. Args: - package_name: If provided, only list crews for this package. - framework: If provided, only list crews that can run on this framework. + package_name: If provided, only list fabric agents for this package. + framework: If provided, only list fabric agents that can run on this framework. Returns: - Dict mapping package name to list of crew info dicts. - Each crew dict includes: - - name: Crew name - - description: Crew description + Dict mapping package name to list of fabric agent info dicts. + Each fabric agent dict includes: + - name: Fabric agent name + - description: Fabric agent description - required_framework: Framework required (if in framework-specific dir) """ packages = discover_packages(framework=framework) @@ -314,16 +315,16 @@ def list_crews( manifest = load_manifest(config_dir) required_framework = get_framework_from_config_dir(config_dir) - crews = [] - for crew_name, crew_config in manifest.get("crews", {}).items(): - crews.append( + fabric_agents = [] + for fabric_agent_name, fabric_agent_config in manifest.get("fabric_agents", {}).items(): + fabric_agents.append( { - "name": crew_name, - "description": crew_config.get("description", ""), + "name": fabric_agent_name, + "description": fabric_agent_config.get("description", ""), "required_framework": required_framework, - "preferred_framework": crew_config.get("preferred_framework"), + "preferred_framework": fabric_agent_config.get("preferred_framework"), } ) - result[pkg_name] = crews + result[pkg_name] = fabric_agents return result diff --git a/packages/agentic-fabric/src/agentic_fabric/core/loader.py b/packages/agentic-fabric/src/agentic_fabric/core/loader.py index 3ea8a4f..ef5e6e8 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/loader.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/loader.py @@ -127,11 +127,11 @@ def create_task_from_config( ) -def load_crew_from_config(crew_config: dict) -> Crew: +def load_fabric_agent_from_config(fabric_agent_config: dict) -> Crew: """Load a complete Crew from configuration. Args: - crew_config: Configuration dict from get_crew_config(). + fabric_agent_config: Configuration dict from get_fabric_agent_config(). Returns: Configured Crew instance ready to kickoff. @@ -155,7 +155,7 @@ def load_crew_from_config(crew_config: dict) -> Crew: read_tools = [code_reader, dir_lister] # Create agents - agents_config = crew_config.get("agents", {}) + agents_config = fabric_agent_config.get("agents", {}) agents: dict[str, Any] = {} for agent_name, agent_cfg in agents_config.items(): @@ -170,7 +170,7 @@ def load_crew_from_config(crew_config: dict) -> Crew: agents[agent_name] = create_agent_from_config(agent_name, agent_cfg, tools) # Create tasks - tasks_config = crew_config.get("tasks", {}) + tasks_config = fabric_agent_config.get("tasks", {}) tasks = [] for task_name, task_cfg in tasks_config.items(): @@ -188,9 +188,9 @@ def load_crew_from_config(crew_config: dict) -> Crew: tasks.append(create_task_from_config(task_name, task_cfg, agent)) # Load knowledge sources - knowledge_sources = load_knowledge_sources(crew_config.get("knowledge_paths", [])) + knowledge_sources = load_knowledge_sources(fabric_agent_config.get("knowledge_paths", [])) - # Create and return the crew + # Create and return the fabric_agent return Crew( agents=list(agents.values()), tasks=tasks, diff --git a/packages/agentic-fabric/src/agentic_fabric/core/manager.py b/packages/agentic-fabric/src/agentic_fabric/core/manager.py index c1d2b93..84f3e1b 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/manager.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/manager.py @@ -1,6 +1,6 @@ -"""Hierarchical Manager Agent for crew orchestration. +"""Hierarchical manager agent for fabric agent orchestration. -This module provides a manager agent that can orchestrate multiple crews, +This module provides a manager agent that can orchestrate multiple fabric agents, enabling complex workflows with smart delegation, sequential and parallel execution, and human-in-the-loop checkpoints. @@ -9,11 +9,11 @@ from agentic_fabric.core.manager import ManagerAgent class GameDevManager(ManagerAgent): - '''Manager that orchestrates game development crews.''' + '''Manager that orchestrates game development fabric agents.''' def __init__(self): super().__init__( - crews={ + fabric_agents={ "design": "gameplay_design", "implementation": "ecs_implementation", "assets": "asset_pipeline", @@ -47,8 +47,8 @@ async def execute_workflow(self, task: str): from pathlib import Path from typing import Any -from agentic_fabric.core.decomposer import run_crew_auto -from agentic_fabric.core.discovery import discover_packages, get_crew_config +from agentic_fabric.core.decomposer import run_fabric_agent_auto +from agentic_fabric.core.discovery import discover_packages, get_fabric_agent_config logger = logging.getLogger(__name__) @@ -57,33 +57,33 @@ async def execute_workflow(self, task: str): class ManagerAgent: """Base class for hierarchical manager agents. - A manager agent orchestrates multiple specialized crews to accomplish + A manager agent orchestrates multiple specialized fabric agents to accomplish complex tasks that require coordination between different domains or phases. Attributes: - crews: Dict mapping crew role names to crew names in packages. - package_name: Optional package name if all crews are in one package. - workspace_root: Optional workspace root for crew discovery. + fabric_agents: Dict mapping fabric agent role names to fabric agent names in packages. + package_name: Optional package name if all fabric agents are in one package. + workspace_root: Optional workspace root for fabric agent discovery. """ def __init__( self, - crews: dict[str, str], + fabric_agents: dict[str, str], package_name: str | None = None, workspace_root: Path | None = None, ): """Initialize the manager agent. Args: - crews: Dict mapping role names to crew names (e.g., {"design": "game_design"}). - package_name: Optional package name if all crews are in the same package. + fabric_agents: Dict mapping role names to fabric agent names (e.g., {"design": "game_design"}). + package_name: Optional package name if all fabric agents are in the same package. workspace_root: Optional workspace root for discovering packages. """ - self.crews = crews + self.fabric_agents = fabric_agents self.package_name = package_name self.workspace_root = workspace_root self._packages_cache: dict[str, Path] | None = None - self._crew_config_cache: dict[str, dict[str, Any]] = {} + self._fabric_agent_config_cache: dict[str, dict[str, Any]] = {} def _get_packages(self) -> dict[str, Path]: """Get discovered packages, using cache if available.""" @@ -93,101 +93,102 @@ def _get_packages(self) -> dict[str, Path]: def delegate( self, - crew_role: str, + fabric_agent_role: str, inputs: dict[str, Any] | str, framework: str | None = None, ) -> str: - """Delegate a task to a specific crew synchronously. + """Delegate a task to a specific fabric agent synchronously. Args: - crew_role: Role name from the crews dict (e.g., "design"). - inputs: Input dict or string to pass to the crew. + fabric_agent_role: Role name from the fabric_agents dict (e.g., "design"). + inputs: Input dict or string to pass to the fabric agent. framework: Optional framework override. Returns: - Crew output as a string. + Fabric agent output as a string. Raises: - ValueError: If crew_role not found in crews mapping. + ValueError: If fabric_agent_role not found in fabric_agents mapping. """ - if crew_role not in self.crews: - raise ValueError(f"Unknown crew role '{crew_role}'. Available: {list(self.crews.keys())}") + if fabric_agent_role not in self.fabric_agents: + raise ValueError(f"Unknown fabric agent role '{fabric_agent_role}'. Available: {list(self.fabric_agents.keys())}") - crew_name = self.crews[crew_role] + fabric_agent_name = self.fabric_agents[fabric_agent_role] # Convert string inputs to dict (create new dict to avoid mutation) if isinstance(inputs, str): inputs = {"task": inputs} - # Check crew config cache first for performance - if crew_name in self._crew_config_cache: - cached_config = self._crew_config_cache[crew_name] - return run_crew_auto(cached_config, inputs=inputs, framework=framework) + # Check fabric agent config cache first for performance + if fabric_agent_name in self._fabric_agent_config_cache: + cached_config = self._fabric_agent_config_cache[fabric_agent_name] + return run_fabric_agent_auto(cached_config, inputs=inputs, framework=framework) - # Get crew configuration - crew_config: dict[str, Any] + # Get fabric agent configuration + fabric_agent_config: dict[str, Any] if self.package_name: # Use specified package packages = self._get_packages() if self.package_name not in packages: raise ValueError(f"Package '{self.package_name}' not found. Available: {list(packages.keys())}") crewai_dir = packages[self.package_name] - crew_config = get_crew_config(crewai_dir, crew_name) + fabric_agent_config = get_fabric_agent_config(crewai_dir, fabric_agent_name) else: - # Auto-discover package containing the crew + # Auto-discover package containing the fabric agent packages = self._get_packages() found_config: dict[str, Any] | None = None for pkg_dir in packages.values(): try: - found_config = get_crew_config(pkg_dir, crew_name) + found_config = get_fabric_agent_config(pkg_dir, fabric_agent_name) break except ValueError: continue if found_config is None: raise ValueError( - f"Crew '{crew_name}' not found in any package. Available packages: {list(packages.keys())}" + f"Fabric agent '{fabric_agent_name}' not found in any package. " + f"Available packages: {list(packages.keys())}" ) - crew_config = found_config + fabric_agent_config = found_config # Cache the config for future calls - self._crew_config_cache[crew_name] = crew_config - return run_crew_auto(crew_config, inputs=inputs, framework=framework) + self._fabric_agent_config_cache[fabric_agent_name] = fabric_agent_config + return run_fabric_agent_auto(fabric_agent_config, inputs=inputs, framework=framework) async def delegate_async( self, - crew_role: str, + fabric_agent_role: str, inputs: dict[str, Any] | str, framework: str | None = None, ) -> str: - """Delegate a task to a specific crew asynchronously. + """Delegate a task to a specific fabric agent asynchronously. - This runs the crew in a thread pool to avoid blocking the event loop. + This runs the fabric agent in a thread pool to avoid blocking the event loop. Args: - crew_role: Role name from the crews dict (e.g., "design"). - inputs: Input dict or string to pass to the crew. + fabric_agent_role: Role name from the fabric_agents dict (e.g., "design"). + inputs: Input dict or string to pass to the fabric agent. framework: Optional framework override. Returns: - Crew output as a string. + Fabric agent output as a string. """ loop = asyncio.get_running_loop() - return await loop.run_in_executor(None, self.delegate, crew_role, inputs, framework) + return await loop.run_in_executor(None, self.delegate, fabric_agent_role, inputs, framework) async def delegate_parallel( self, delegations: list[tuple[str, dict[str, Any] | str]], framework: str | None = None, ) -> list[str]: - """Delegate tasks to multiple crews in parallel. + """Delegate tasks to multiple fabric agents in parallel. Args: - delegations: List of (crew_role, inputs) tuples. - framework: Optional framework override for all crews. + delegations: List of (fabric_agent_role, inputs) tuples. + framework: Optional framework override for all fabric agents. Returns: - List of crew outputs in the same order as delegations. + List of fabric_agent outputs in the same order as delegations. Example: ```python @@ -198,7 +199,7 @@ async def delegate_parallel( design_result, assets_result = results ``` """ - tasks = [self.delegate_async(crew_role, inputs, framework) for crew_role, inputs in delegations] + tasks = [self.delegate_async(fabric_agent_role, inputs, framework) for fabric_agent_role, inputs in delegations] return await asyncio.gather(*tasks) def delegate_sequential( @@ -206,14 +207,14 @@ def delegate_sequential( delegations: list[tuple[str, dict[str, Any] | str]], framework: str | None = None, ) -> list[str]: - """Delegate tasks to multiple crews sequentially. + """Delegate tasks to multiple fabric agents sequentially. Args: - delegations: List of (crew_role, inputs) tuples. - framework: Optional framework override for all crews. + delegations: List of (fabric_agent_role, inputs) tuples. + framework: Optional framework override for all fabric agents. Returns: - List of crew outputs in the same order as delegations. + List of fabric_agent outputs in the same order as delegations. Example: ```python @@ -225,8 +226,8 @@ def delegate_sequential( ``` """ results = [] - for crew_role, inputs in delegations: - result = self.delegate(crew_role, inputs, framework) + for fabric_agent_role, inputs in delegations: + result = self.delegate(fabric_agent_role, inputs, framework) results.append(result) return results diff --git a/packages/agentic-fabric/src/agentic_fabric/core/runner.py b/packages/agentic-fabric/src/agentic_fabric/core/runner.py index baf56ea..8dbadc0 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/runner.py @@ -1,32 +1,32 @@ -"""Runner module - executes crews with inputs.""" +"""Runner module - executes fabric agents with inputs.""" from __future__ import annotations from pathlib import Path -from agentic_fabric.core.discovery import discover_packages, get_crew_config -from agentic_fabric.core.loader import load_crew_from_config +from agentic_fabric.core.discovery import discover_packages, get_fabric_agent_config +from agentic_fabric.core.loader import load_fabric_agent_from_config -def run_crew( +def run_fabric_agent( package_name: str, - crew_name: str, + fabric_agent_name: str, inputs: dict | None = None, workspace_root: Path | None = None, ) -> str: - """Run a crew from a package with the given inputs. + """Run a fabric agent from a package with the given inputs. Args: package_name: Name of the package. - crew_name: Name of the crew to run (e.g., 'game_builder'). - inputs: Optional dict of inputs to pass to the crew. + fabric_agent_name: Name of the fabric agent to run (e.g., 'game_builder'). + inputs: Optional dict of inputs to pass to the fabric agent. workspace_root: Optional workspace root path. Returns: - The crew's output as a string. + The fabric agent's output as a string. Raises: - ValueError: If package or crew not found. + ValueError: If package or fabric agent not found. """ # Discover packages packages = discover_packages(workspace_root) @@ -35,36 +35,36 @@ def run_crew( available = list(packages.keys()) raise ValueError(f"Package '{package_name}' not found. Available: {available}") - crewai_dir = packages[package_name] + config_dir = packages[package_name] - # Load crew configuration - crew_config = get_crew_config(crewai_dir, crew_name) + # Load fabric agent configuration + fabric_agent_config = get_fabric_agent_config(config_dir, fabric_agent_name) - # Build the crew - crew = load_crew_from_config(crew_config) + # Build the fabric agent + fabric_agent = load_fabric_agent_from_config(fabric_agent_config) # Run it - result = crew.kickoff(inputs=inputs or {}) + result = fabric_agent.kickoff(inputs=inputs or {}) return result.raw if hasattr(result, "raw") else str(result) -def run_crew_from_path( - crewai_dir: Path, - crew_name: str, +def run_fabric_agent_from_path( + config_dir: Path, + fabric_agent_name: str, inputs: dict | None = None, ) -> str: - """Run a crew directly from a .crewai/ directory path. + """Run a fabric agent directly from a fabric config directory path. Args: - crewai_dir: Path to the .crewai/ directory. - crew_name: Name of the crew to run. - inputs: Optional dict of inputs to pass to the crew. + config_dir: Path to the .fabric/ or runtime-specific config directory. + fabric_agent_name: Name of the fabric agent to run. + inputs: Optional dict of inputs to pass to the fabric agent. Returns: - The crew's output as a string. + The fabric agent's output as a string. """ - crew_config = get_crew_config(crewai_dir, crew_name) - crew = load_crew_from_config(crew_config) - result = crew.kickoff(inputs=inputs or {}) + fabric_agent_config = get_fabric_agent_config(config_dir, fabric_agent_name) + fabric_agent = load_fabric_agent_from_config(fabric_agent_config) + result = fabric_agent.kickoff(inputs=inputs or {}) return result.raw if hasattr(result, "raw") else str(result) diff --git a/packages/agentic-fabric/src/agentic_fabric/crews/__init__.py b/packages/agentic-fabric/src/agentic_fabric/crews/__init__.py deleted file mode 100644 index 4719d9a..0000000 --- a/packages/agentic-fabric/src/agentic_fabric/crews/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Built-in crew definitions.""" diff --git a/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/__init__.py b/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/__init__.py deleted file mode 100644 index 57583ec..0000000 --- a/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Connector-builder crew.""" diff --git a/packages/agentic-fabric/src/agentic_fabric/fabric_agents/__init__.py b/packages/agentic-fabric/src/agentic_fabric/fabric_agents/__init__.py new file mode 100644 index 0000000..77a96a8 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/fabric_agents/__init__.py @@ -0,0 +1 @@ +"""Built-in fabric agent definitions.""" diff --git a/packages/agentic-fabric/src/agentic_fabric/fabric_agents/connector_builder/__init__.py b/packages/agentic-fabric/src/agentic_fabric/fabric_agents/connector_builder/__init__.py new file mode 100644 index 0000000..1968cc7 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/fabric_agents/connector_builder/__init__.py @@ -0,0 +1 @@ +"""Connector-builder fabric agent.""" diff --git a/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/config/agents.yaml b/packages/agentic-fabric/src/agentic_fabric/fabric_agents/connector_builder/config/agents.yaml similarity index 100% rename from packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/config/agents.yaml rename to packages/agentic-fabric/src/agentic_fabric/fabric_agents/connector_builder/config/agents.yaml diff --git a/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/config/tasks.yaml b/packages/agentic-fabric/src/agentic_fabric/fabric_agents/connector_builder/config/tasks.yaml similarity index 100% rename from packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/config/tasks.yaml rename to packages/agentic-fabric/src/agentic_fabric/fabric_agents/connector_builder/config/tasks.yaml diff --git a/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/connector_builder_crew.py b/packages/agentic-fabric/src/agentic_fabric/fabric_agents/connector_builder/connector_builder_fabric.py similarity index 71% rename from packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/connector_builder_crew.py rename to packages/agentic-fabric/src/agentic_fabric/fabric_agents/connector_builder/connector_builder_fabric.py index 591b5a3..257424e 100644 --- a/packages/agentic-fabric/src/agentic_fabric/crews/connector_builder/connector_builder_crew.py +++ b/packages/agentic-fabric/src/agentic_fabric/fabric_agents/connector_builder/connector_builder_fabric.py @@ -1,6 +1,6 @@ -"""Connector Builder Crew +"""Connector builder fabric agent. -This script creates and inits the 'connector_builder' crew, which is +This script creates and initializes the ``connector_builder`` fabric agent, which is designed to automatically generate HTTP connector code by scraping API documentation. """ @@ -21,7 +21,7 @@ def _load_crewai_classes() -> tuple[Any, Any, Any]: - """Load CrewAI classes only when the connector-builder crew is used.""" + """Load CrewAI classes only when the connector-builder fabric agent is used.""" global Agent, Crew, Task if Agent is None or Crew is None or Task is None: @@ -30,7 +30,7 @@ def _load_crewai_classes() -> tuple[Any, Any, Any]: from crewai import Crew as ImportedCrew from crewai import Task as ImportedTask except ImportError as exc: - msg = f"ConnectorBuilderCrew requires CrewAI. Install with: {install_command('crewai')}" + msg = f"ConnectorBuilderFabricAgent requires CrewAI. Install with: {install_command('crewai')}" raise RuntimeError(msg) from exc if Agent is None: @@ -43,21 +43,20 @@ def _load_crewai_classes() -> tuple[Any, Any, Any]: return Agent, Crew, Task -class ConnectorBuilderCrew: - """Manages the agents and tasks for the connector builder crew. +class ConnectorBuilderFabricAgent: + """Manage the agents and tasks for the connector builder fabric agent. - This class loads agent and task configurations from YAML files, - instantiates the necessary CrewAI components, and provides a method - to - execute the crew's workflow. + This class loads agent and task configurations from YAML files, instantiates + the necessary CrewAI components, and provides a method to execute the + fabric agent's workflow. Attributes: - crew: An instance of the CrewAI Crew, configured with agents and - tasks for connector building. + fabric_agent: An instance of the CrewAI Crew configured with agents and + tasks for connector building. """ def __init__(self, output_dir: str = "output"): - """Initializes the ConnectorBuilderCrew. + """Initializes the ConnectorBuilderFabricAgent. Loads agent and task configurations from YAML files, creates Agent and Task objects, and assembles them into a Crew. @@ -89,21 +88,21 @@ def build_agent(name: str) -> Any: generate_code_config["description"] = generate_code_config["description"].format(output_dir=output_dir) self.generate_code = task_cls(**generate_code_config) - self.crew = crew_cls( + self.fabric_agent = crew_cls( agents=[self.doc_scraper, self.api_analyzer, self.code_generator], tasks=[self.scrape_docs, self.analyze_api, self.generate_code], verbose=True, ) def kickoff(self, inputs: dict) -> str: - """Starts the crew's execution with the given inputs. + """Start the fabric agent's execution with the given inputs. Args: inputs: A dictionary containing the necessary inputs for the - crew's tasks, such as the URL of the API documentation. + fabric agent's tasks, such as the URL of the API documentation. Returns: - A string representing the result of the crew's execution. + A string representing the result of the fabric agent's execution. """ - result = self.crew.kickoff(inputs=inputs) + result = self.fabric_agent.kickoff(inputs=inputs) return result.raw if hasattr(result, "raw") else str(result) diff --git a/packages/agentic-fabric/src/agentic_fabric/main.py b/packages/agentic-fabric/src/agentic_fabric/main.py index b2488e2..10c377a 100644 --- a/packages/agentic-fabric/src/agentic_fabric/main.py +++ b/packages/agentic-fabric/src/agentic_fabric/main.py @@ -1,24 +1,24 @@ -"""Main entry point for agentic-fabric - Framework-Agnostic Crew Runner. +"""Main entry point for agentic-fabric - framework-agnostic fabric agent runner. -This is a generic crew runner that discovers and executes crews -defined in packages' .crew/, .crewai/, .langgraph/, or .strands/ directories. +This is a generic fabric agent runner that discovers and executes fabric agents +defined in packages' .fabric/, .crewai/, .langgraph/, or .strands/ directories. Usage: - # List all available packages with crews + # List all available packages with fabric agents agentic-fabric list agentic-fabric list --json # JSON output for external tools - # List crews in a specific package + # List fabric agents in a specific package agentic-fabric list my-package - # Run a crew + # Run a fabric agent agentic-fabric run my-package reviewer --input "Review this code" agentic-fabric run my-package reviewer --input "..." --json # JSON output # Run with input from file agentic-fabric run my-package reviewer --file tasks.md - # Show crew details + # Show fabric agent details agentic-fabric info my-package reviewer --json """ @@ -31,60 +31,60 @@ from pathlib import Path -from agentic_fabric.core.discovery import discover_packages, get_crew_config, list_crews +from agentic_fabric.core.discovery import discover_packages, get_fabric_agent_config, list_fabric_agents def cmd_list(args): - """List available packages and crews.""" + """List available packages and fabric agents.""" framework = getattr(args, "framework", None) use_json = getattr(args, "json", False) - crews_by_package = list_crews( + fabric_agents_by_package = list_fabric_agents( args.package if hasattr(args, "package") else None, framework=framework, ) if use_json: # Flatten to list for JSON output - all_crews = [] - for pkg_name, crews in crews_by_package.items(): - for crew in crews: - all_crews.append( + all_fabric_agents = [] + for pkg_name, fabric_agents in fabric_agents_by_package.items(): + for fabric_agent in fabric_agents: + all_fabric_agents.append( { "package": pkg_name, - "name": crew["name"], - "description": crew.get("description", ""), - "required_framework": crew.get("required_framework"), + "name": fabric_agent["name"], + "description": fabric_agent.get("description", ""), + "required_framework": fabric_agent.get("required_framework"), } ) - print(json.dumps({"crews": all_crews}, indent=2)) + print(json.dumps({"fabric_agents": all_fabric_agents}, indent=2)) return - if not crews_by_package: - print("No packages with crew configuration directories found.") - print("\nTo add crews to a package, create one of:") - print(" packages//.crew/manifest.yaml # Framework-agnostic") + if not fabric_agents_by_package: + print("No packages with fabric agent configuration directories found.") + print("\nTo add fabric agents to a package, create one of:") + print(" packages//.fabric/manifest.yaml # Framework-agnostic") print(" packages//.crewai/manifest.yaml # CrewAI-specific") print(" packages//.langgraph/manifest.yaml # LangGraph-specific") print(" packages//.strands/manifest.yaml # Strands-specific") return print("=" * 60) - print("AVAILABLE CREWS") + print("AVAILABLE FABRIC AGENTS") print("=" * 60) - for pkg_name, crews in crews_by_package.items(): + for pkg_name, fabric_agents in fabric_agents_by_package.items(): print(f"\n📦 {pkg_name}") - for crew in crews: - desc = crew.get("description", "") + for fabric_agent in fabric_agents: + desc = fabric_agent.get("description", "") framework_info = "" - if crew.get("required_framework"): - framework_info = f" [{crew['required_framework']}]" - print(f" • {crew['name']}{framework_info}: {desc}") + if fabric_agent.get("required_framework"): + framework_info = f" [{fabric_agent['required_framework']}]" + print(f" • {fabric_agent['name']}{framework_info}: {desc}") def cmd_run(args): - """Run a specific crew or single-agent task.""" + """Run a specific fabric agent or single-agent task.""" use_json = getattr(args, "json", False) start_time = time.time() @@ -92,30 +92,30 @@ def cmd_run(args): if hasattr(args, "runner") and args.runner: return _cmd_run_single_agent(args, use_json, start_time) - # Multi-agent crew execution requires package and crew - if not args.package or not args.crew: + # Multi-agent fabric agent execution requires package and fabric_agent. + if not args.package or not args.fabric_agent: if use_json: print( json.dumps( { "success": False, - "error": "Package and crew are required for multi-agent execution. " + "error": "Package and fabric_agent are required for multi-agent execution. " "Use --runner for single-agent tasks.", "duration_ms": int((time.time() - start_time) * 1000), } ) ) else: - print("❌ Error: Package and crew are required for multi-agent execution.") - print("Use --runner for single-agent tasks or provide both package and crew.") + print("❌ Error: Package and fabric_agent are required for multi-agent execution.") + print("Use --runner for single-agent tasks or provide both package and fabric_agent.") sys.exit(2) - # Multi-agent crew execution (existing logic) - from agentic_fabric.core.decomposer import detect_framework, run_crew_auto + # Multi-agent fabric agent execution + from agentic_fabric.core.decomposer import detect_framework, run_fabric_agent_auto if not use_json: print("=" * 60) - print(f"🚀 Running {args.package}/{args.crew}") + print(f"🚀 Running {args.package}/{args.fabric_agent}") print("=" * 60) try: @@ -158,10 +158,10 @@ def cmd_run(args): config_dir = packages[args.package] try: - crew_config = get_crew_config(config_dir, args.crew) + fabric_agent_config = get_fabric_agent_config(config_dir, args.fabric_agent) # Determine framework - required = crew_config.get("required_framework") + required = fabric_agent_config.get("required_framework") requested = args.framework if args.framework != "auto" else None framework_used = required or requested or detect_framework() @@ -173,8 +173,8 @@ def cmd_run(args): else: print(f"📋 Framework: {framework_used} (auto-detected)") - result = run_crew_auto( - crew_config, + result = run_fabric_agent_auto( + fabric_agent_config, inputs=inputs, framework=framework_used, ) @@ -212,7 +212,7 @@ def cmd_run(args): ) else: print(f"❌ Error: {e}") - sys.exit(1) # Exit code 1 = crew execution failed + sys.exit(1) # Exit code 1 = fabric agent execution failed def _cmd_run_single_agent(args, use_json: bool, start_time: float): @@ -340,7 +340,7 @@ def _cmd_run_single_agent(args, use_json: bool, start_time: float): def cmd_info(args): - """Show detailed info about a crew.""" + """Show detailed info about a fabric agent.""" use_json = getattr(args, "json", False) packages = discover_packages() @@ -362,7 +362,7 @@ def cmd_info(args): config_dir = packages[args.package] try: - config = get_crew_config(config_dir, args.crew) + config = get_fabric_agent_config(config_dir, args.fabric_agent) except ValueError as e: if use_json: print(json.dumps({"error": str(e)})) @@ -375,7 +375,7 @@ def cmd_info(args): json.dumps( { "package": args.package, - "name": args.crew, + "name": args.fabric_agent, "description": config.get("description", ""), "required_framework": config.get("required_framework"), "agents": [ @@ -393,7 +393,7 @@ def cmd_info(args): return print("=" * 60) - print(f"CREW: {args.package}/{args.crew}") + print(f"FABRIC AGENT: {args.package}/{args.fabric_agent}") print("=" * 60) print(f"\nDescription: {config.get('description', 'N/A')}") @@ -487,22 +487,22 @@ def cmd_list_runners(args): def main(): """CLI entry point.""" parser = argparse.ArgumentParser( - description="agentic-fabric - Framework-Agnostic Crew Runner", + description="agentic-fabric - framework-agnostic fabric agent runner", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: - # List all packages with crews + # List all packages with fabric agents agentic-fabric list agentic-fabric list --json # JSON output for external tools - # List crews in a package + # List fabric agents in a package agentic-fabric list my-package # List available single-agent runners agentic-fabric list-runners agentic-fabric list-runners --json - # Run a multi-agent crew + # Run a multi-agent fabric agent agentic-fabric run my-package reviewer --input "Review this code" agentic-fabric run my-package reviewer --input "..." --json # JSON output @@ -511,25 +511,25 @@ def main(): agentic-fabric run --runner claude-code --input "Refactor the database module" agentic-fabric run --runner ollama --input "Fix the bug" --model deepseek-coder - # Show crew details + # Show fabric agent details agentic-fabric info my-package reviewer --json Exit codes: 0 - Success - 1 - Crew execution failed - 2 - Configuration error (package/crew not found) + 1 - Fabric agent execution failed + 2 - Configuration error (package/fabric_agent not found) """, ) subparsers = parser.add_subparsers(dest="command", help="Available commands") # List command - list_parser = subparsers.add_parser("list", help="List available crews") - list_parser.add_argument("package", nargs="?", help="Package to list crews for") + list_parser = subparsers.add_parser("list", help="List available fabric agents") + list_parser.add_argument("package", nargs="?", help="Package to list fabric agents for") list_parser.add_argument( "--framework", choices=["crewai", "langgraph", "strands"], - help="Filter crews by framework", + help="Filter fabric agents by framework", ) list_parser.add_argument("--json", action="store_true", help="Output as JSON (for external tools)") @@ -538,9 +538,9 @@ def main(): list_runners_parser.add_argument("--json", action="store_true", help="Output as JSON (for external tools)") # Run command - run_parser = subparsers.add_parser("run", help="Run a crew or single-agent task") + run_parser = subparsers.add_parser("run", help="Run a fabric agent or single-agent task") run_parser.add_argument("package", nargs="?", help="Package name (e.g., my-package)") - run_parser.add_argument("crew", nargs="?", help="Crew name (e.g., reviewer)") + run_parser.add_argument("fabric_agent", nargs="?", help="Fabric agent name (e.g., reviewer)") run_parser.add_argument("--input", "-i", help="Input specification") run_parser.add_argument("--file", "-f", help="Read input from file") run_parser.add_argument( @@ -548,12 +548,12 @@ def main(): choices=["auto", "crewai", "langgraph", "strands"], default="auto", help="Framework to use (auto=detect, or specify). " - "Note: If crew is in a framework-specific directory, that takes precedence.", + "Note: If the fabric agent is in a framework-specific directory, that takes precedence.", ) run_parser.add_argument( "--runner", help="Single-agent CLI runner to use (e.g., aider, claude-code, ollama). " - "When specified, package/crew are optional and task is run with the CLI tool.", + "When specified, package/fabric_agent are optional and task is run with the CLI tool.", ) run_parser.add_argument( "--model", @@ -568,9 +568,9 @@ def main(): run_parser.add_argument("--json", action="store_true", help="Output as JSON (for external tools)") # Info command - info_parser = subparsers.add_parser("info", help="Show crew details") + info_parser = subparsers.add_parser("info", help="Show fabric agent details") info_parser.add_argument("package", help="Package name") - info_parser.add_argument("crew", help="Crew name") + info_parser.add_argument("fabric_agent", help="Fabric agent name") info_parser.add_argument("--json", action="store_true", help="Output as JSON (for external tools)") args = parser.parse_args() diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/__init__.py b/packages/agentic-fabric/src/agentic_fabric/runners/__init__.py index 55b9448..f4cc18b 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/__init__.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/__init__.py @@ -1,7 +1,7 @@ """Framework-specific runners for agentic-fabric. Each runner implements the same interface but targets a different AI framework: -- CrewAIRunner: Full-featured, best for complex crews +- CrewAIRunner: Full-featured, best for complex fabric_agents - LangGraphRunner: Graph-based flows, good for conditional logic - StrandsRunner: Lightweight, AWS-native @@ -9,12 +9,12 @@ - LocalCLIRunner: Universal runner for any CLI-based coding agent (aider, claude-code, ollama, etc.) Usage: - # Multi-agent crews + # Multi-agent fabric_agents from agentic_fabric.runners import get_runner runner = get_runner("crewai") # Or "langgraph", "strands", "auto" - crew = runner.build_crew(config) - result = runner.run(crew, inputs) + fabric_agent = runner.build_fabric_agent(config) + result = runner.run(fabric_agent, inputs) # Single-agent CLI runners from agentic_fabric.core.decomposer import get_cli_runner diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/base.py b/packages/agentic-fabric/src/agentic_fabric/runners/base.py index 9e16902..7a0d08c 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/base.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/base.py @@ -16,8 +16,8 @@ class MyFrameworkRunner(BaseRunner): ``` 2. **Implement required methods**: - - `build_crew()` - Convert universal config to framework crew - - `run()` - Execute the crew and return string output + - `build_fabric_agent()` - Convert universal config to a runtime-native fabric agent + - `run()` - Execute the fabric agent and return string output - `build_agent()` - Create framework-specific agent - `build_task()` - Create framework-specific task @@ -43,22 +43,22 @@ def __init__(self): except ImportError as e: raise RuntimeError("simple_framework not installed") from e - def build_crew(self, crew_config): + def build_fabric_agent(self, fabric_agent_config): # Use dict to allow lookup by agent name agents = { name: self.build_agent(cfg) - for name, cfg in crew_config.get("agents", {}).items() + for name, cfg in fabric_agent_config.get("agents", {}).items() } tasks = [] - for task_name, task_cfg in crew_config.get("tasks", {}).items(): + for task_name, task_cfg in fabric_agent_config.get("tasks", {}).items(): agent_name = task_cfg.get("agent") agent = agents[agent_name] tasks.append(self.build_task(task_cfg, agent)) return {"agents": list(agents.values()), "tasks": tasks} - def run(self, crew, inputs): + def run(self, fabric_agent, inputs): # Execute and return string return "Result from simple framework" @@ -81,7 +81,7 @@ def build_task(self, task_config, agent): class BaseRunner(AgentCapabilityProviderMixin, ABC): """Abstract base class for framework runners. - Each framework runner converts agentic-fabric's universal crew format + Each framework runner converts agentic-fabric's universal fabric agent format into framework-specific objects and executes them. Attributes: @@ -97,8 +97,8 @@ class BaseRunner(AgentCapabilityProviderMixin, ABC): # Auto-detect best available framework runner = get_runner() - crew_config = { - "name": "research_crew", + fabric_agent_config = { + "name": "research_fabric_agent", "agents": { "researcher": { "role": "Senior Researcher", @@ -116,7 +116,7 @@ class BaseRunner(AgentCapabilityProviderMixin, ABC): } # Build and run in one step - result = runner.build_and_run(crew_config, {"topic": "AI safety"}) + result = runner.build_and_run(fabric_agent_config, {"topic": "AI safety"}) print(result) ``` """ @@ -124,20 +124,20 @@ class BaseRunner(AgentCapabilityProviderMixin, ABC): framework_name: str = "base" @runtime_capability( - "build_crew", - description="Convert universal crew configuration into a runtime-native crew object.", + "build_fabric_agent", + description="Convert universal fabric agent configuration into a runtime-native fabric agent object.", ) @abstractmethod - def build_crew(self, crew_config: dict[str, Any]) -> Any: - """Build a framework-specific crew from configuration. + def build_fabric_agent(self, fabric_agent_config: dict[str, Any]) -> Any: + """Build a framework-specific fabric agent from configuration. This is the main method that converts agentic-fabric's universal - YAML-based crew configuration into the framework's native objects. + YAML-based fabric agent configuration into the framework's native objects. Args: - crew_config: Universal crew configuration dict containing: - - name (str): Crew name for identification - - description (str): What the crew does + fabric_agent_config: Universal fabric agent configuration dict containing: + - name (str): Fabric agent name for identification + - description (str): What the fabric agent does - agents (dict): Dict mapping agent names to agent configs. Each agent config has: - role (str): Agent's role/title @@ -158,7 +158,7 @@ def build_crew(self, crew_config: dict[str, Any]) -> Any: - required_framework (str, optional): Enforced framework Returns: - Framework-specific crew object. Type varies by framework: + Framework-specific fabric agent object. Type varies by framework: - CrewAI: crewai.Crew - LangGraph: Compiled StateGraph - Strands: strands.Agent @@ -171,8 +171,8 @@ def build_crew(self, crew_config: dict[str, Any]) -> Any: Example: ```python runner = CrewAIRunner() - crew = runner.build_crew({ - "name": "writer_crew", + fabric_agent = runner.build_fabric_agent({ + "name": "writer_fabric_agent", "agents": { "writer": { "role": "Technical Writer", @@ -191,36 +191,36 @@ def build_crew(self, crew_config: dict[str, Any]) -> Any: ``` """ - @runtime_capability("run", description="Execute a runtime-native crew object.") + @runtime_capability("run", description="Execute a runtime-native fabric agent object.") @abstractmethod - def run(self, crew: Any, inputs: dict[str, Any]) -> str: - r"""Execute the crew with inputs. + def run(self, fabric_agent: Any, inputs: dict[str, Any]) -> str: + r"""Execute the fabric agent with inputs. - Runs the previously built crew with the given inputs. This method + Runs the previously built fabric agent with the given inputs. This method handles the framework-specific execution and normalizes the output to a string. Args: - crew: Framework-specific crew object from build_crew(). + fabric_agent: Framework-specific fabric agent object from build_fabric_agent(). Type depends on framework: - CrewAI: crewai.Crew instance - LangGraph: Compiled StateGraph - Strands: strands.Agent instance - inputs: Input dict to pass to the crew. Keys are typically + inputs: Input dict to pass to the fabric agent. Keys are typically variable names used in task descriptions (e.g., {topic} in description becomes inputs["topic"]). Returns: - Crew output as a string. For complex outputs, the raw text + Fabric agent output as a string. For complex outputs, the raw text is returned; parsing is left to the caller. Raises: - RuntimeError: If crew execution fails. + RuntimeError: If fabric agent execution fails. Example: ```python # CrewAI runner example - result = runner.run(crew, {"topic": "machine learning"}) + result = runner.run(fabric_agent, {"topic": "machine learning"}) print(result) # "## Machine Learning Report\\n\\n..." # LangGraph runner example @@ -228,23 +228,23 @@ def run(self, crew: Any, inputs: dict[str, Any]) -> str: ``` """ - @runtime_capability("build_and_run", aliases=("run_crew",), description="Build and execute a crew in one call.") + @runtime_capability("build_and_run", aliases=("run_fabric_agent",), description="Build and execute a fabric agent in one call.") def build_and_run( self, - crew_config: dict[str, Any], + fabric_agent_config: dict[str, Any], inputs: dict[str, Any] | None = None, ) -> str: """Convenience method to build and run in one step. Args: - crew_config: Crew configuration. - inputs: Optional inputs for the crew. + fabric_agent_config: Fabric agent configuration. + inputs: Optional inputs for the fabric agent. Returns: - Crew output as string. + Fabric agent output as string. """ - crew = self.build_crew(crew_config) - return self.run(crew, inputs or {}) + fabric_agent = self.build_fabric_agent(fabric_agent_config) + return self.run(fabric_agent, inputs or {}) @runtime_capability("build_agent", description="Create one runtime-native agent object.") @abstractmethod @@ -252,7 +252,7 @@ def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) - """Build a framework-specific agent. Creates a single agent from the universal configuration. Called by - build_crew() for each agent in the crew. + build_fabric_agent() for each agent in the fabric agent. Args: agent_config: Agent configuration dict containing: @@ -288,7 +288,7 @@ def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: """Build a framework-specific task. Creates a single task from the universal configuration. Called by - build_crew() for each task in the crew. + build_fabric_agent() for each task in the fabric agent. Note: Some frameworks (like CrewAI) support additional parameters beyond the base signature. Subclasses may extend this method with diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py index 159a418..2642f68 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/crewai_runner.py @@ -24,7 +24,7 @@ class CrewAIRunner(BaseRunner): - """Runner that uses CrewAI for crew execution.""" + """Runner that uses CrewAI for fabric agent execution.""" framework_name = "crewai" @@ -36,11 +36,11 @@ def __init__(self): except ImportError as e: raise RuntimeError(f"CrewAI not installed. Install with: {install_command(self.framework_name)}") from e - def build_crew(self, crew_config: dict[str, Any]) -> Any: + def build_fabric_agent(self, fabric_agent_config: dict[str, Any]) -> Any: """Build a CrewAI Crew from configuration. Args: - crew_config: Universal crew configuration. + fabric_agent_config: Universal fabric agent configuration. Returns: CrewAI Crew object. @@ -48,7 +48,7 @@ def build_crew(self, crew_config: dict[str, Any]) -> Any: from crewai import Crew, Process # Build agents with their tools from config - agents_config = crew_config.get("agents", {}) + agents_config = fabric_agent_config.get("agents", {}) agents = {} for agent_name, agent_cfg in agents_config.items(): # Extract tools from agent config @@ -56,7 +56,7 @@ def build_crew(self, crew_config: dict[str, Any]) -> Any: agents[agent_name] = self.build_agent(agent_cfg, tools=agent_tools) # Build tasks, tracking them for context dependencies - tasks_config = crew_config.get("tasks", {}) + tasks_config = fabric_agent_config.get("tasks", {}) tasks = [] tasks_by_name: dict[str, Any] = {} for task_name, task_cfg in tasks_config.items(): @@ -85,33 +85,33 @@ def build_crew(self, crew_config: dict[str, Any]) -> Any: tasks_by_name[task_name] = task # Load knowledge sources - knowledge_sources = self._load_knowledge(crew_config.get("knowledge_paths", [])) + knowledge_sources = self._load_knowledge(fabric_agent_config.get("knowledge_paths", [])) # Determine process type - process_type = crew_config.get("process", "sequential") + process_type = fabric_agent_config.get("process", "sequential") process = Process.hierarchical if process_type == "hierarchical" else Process.sequential return Crew( agents=list(agents.values()), tasks=tasks, process=process, - planning=crew_config.get("planning", True), - memory=crew_config.get("memory", True), + planning=fabric_agent_config.get("planning", True), + memory=fabric_agent_config.get("memory", True), knowledge_sources=knowledge_sources or None, - verbose=crew_config.get("verbose", True), + verbose=fabric_agent_config.get("verbose", True), ) - def run(self, crew: Any, inputs: dict[str, Any]) -> str: - """Execute the CrewAI crew. + def run(self, fabric_agent: Any, inputs: dict[str, Any]) -> str: + """Execute the CrewAI fabric agent. Args: - crew: CrewAI Crew object. - inputs: Inputs for the crew. + fabric_agent: CrewAI Crew object. + inputs: Inputs for the fabric agent. Returns: - Crew output as string. + Fabric agent output as string. """ - result = crew.kickoff(inputs=inputs) + result = fabric_agent.kickoff(inputs=inputs) return result.raw if hasattr(result, "raw") else str(result) def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py index b257fb7..105755e 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/langgraph_runner.py @@ -17,7 +17,7 @@ class LangGraphRunner(BaseRunner): - """Runner that uses LangGraph for crew execution.""" + """Runner that uses LangGraph for fabric agent execution.""" framework_name = "langgraph" @@ -28,38 +28,38 @@ def __init__(self): except ImportError as e: raise RuntimeError(f"LangGraph not installed. Install with: {install_command(self.framework_name)}") from e - def build_crew(self, crew_config: dict[str, Any]) -> Any: - """Build a LangGraph ReAct agent from crew configuration. + def build_fabric_agent(self, fabric_agent_config: dict[str, Any]) -> Any: + """Build a LangGraph ReAct agent from fabric agent configuration. - The current adapter condenses the universal crew configuration into a + The current adapter condenses the universal fabric agent configuration into a LangGraph prebuilt ReAct agent with the declared tools attached. Args: - crew_config: Universal crew configuration. + fabric_agent_config: Universal fabric agent configuration. Returns: LangGraph runnable agent. """ from langgraph.prebuilt import create_react_agent - # For simple crews, create a ReAct agent - # More complex crews could be converted to full StateGraphs + # For simple fabric agents, create a ReAct agent. + # More complex fabric agents could be converted to full StateGraphs. # Get LLM from config (respects the framework's configuration) - llm_config = crew_config.get("llm", {}) + llm_config = fabric_agent_config.get("llm", {}) model = llm_config.get("model") if isinstance(llm_config, dict) else llm_config llm = self.get_llm(model) # Build tools declared by agents in the universal config - tools = self._build_tools_from_config(crew_config) + tools = self._build_tools_from_config(fabric_agent_config) return create_react_agent(llm, tools) - def run(self, crew: Any, inputs: dict[str, Any]) -> str: + def run(self, fabric_agent: Any, inputs: dict[str, Any]) -> str: """Execute the LangGraph workflow. Args: - crew: Compiled LangGraph. + fabric_agent: Compiled LangGraph. inputs: Inputs for the workflow. Returns: @@ -68,7 +68,7 @@ def run(self, crew: Any, inputs: dict[str, Any]) -> str: # Convert inputs to messages format user_message = inputs.get("input", inputs.get("task", str(inputs))) - result = crew.invoke({"messages": [("user", user_message)]}) + result = fabric_agent.invoke({"messages": [("user", user_message)]}) # Extract final message messages = result.get("messages", []) @@ -128,9 +128,9 @@ def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: "agent": agent, } - def _build_tools_from_config(self, crew_config: dict[str, Any]) -> list: + def _build_tools_from_config(self, fabric_agent_config: dict[str, Any]) -> list: """Resolve configured agent tools into LangGraph-compatible tools.""" tool_names: list[str] = [] - for agent_cfg in crew_config.get("agents", {}).values(): + for agent_cfg in fabric_agent_config.get("agents", {}).values(): tool_names.extend(agent_cfg.get("tools", [])) return resolve_langgraph_tools(tool_names) diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/single_agent_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/single_agent_runner.py index 0566683..bc22f8d 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/single_agent_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/single_agent_runner.py @@ -7,7 +7,7 @@ - Local development - Sequential workflows -Unlike multi-agent crews, single-agent runners: +Unlike multi-agent fabric agents, single-agent runners: - Take a single task string as input - Execute in one pass without delegation - Return results directly @@ -26,7 +26,7 @@ class SingleAgentRunner(AgentCapabilityProviderMixin, ABC): """Base class for single-agent runners. Single-agent runners provide a simpler execution model than multi-agent - crews. They execute a single task and return the result, without the + fabric agents. They execute a single task and return the result, without the complexity of agent collaboration, delegation, or hierarchical processes. """ diff --git a/packages/agentic-fabric/src/agentic_fabric/runners/strands_runner.py b/packages/agentic-fabric/src/agentic_fabric/runners/strands_runner.py index dfb57d9..5ff1021 100644 --- a/packages/agentic-fabric/src/agentic_fabric/runners/strands_runner.py +++ b/packages/agentic-fabric/src/agentic_fabric/runners/strands_runner.py @@ -17,7 +17,7 @@ class StrandsRunner(BaseRunner): - """Runner that uses AWS Strands for crew execution.""" + """Runner that uses AWS Strands for fabric agent execution.""" framework_name = "strands" @@ -28,28 +28,28 @@ def __init__(self): except ImportError as e: raise RuntimeError(f"Strands not installed. Install with: {install_command(self.framework_name)}") from e - def build_crew(self, crew_config: dict[str, Any]) -> Any: + def build_fabric_agent(self, fabric_agent_config: dict[str, Any]) -> Any: """Build a Strands agent from configuration. - Strands uses a single-agent model, so we combine crew tasks + Strands uses a single-agent model, so we combine fabric agent tasks into a system prompt for one capable agent. Args: - crew_config: Universal crew configuration. + fabric_agent_config: Universal fabric agent configuration. Returns: Strands Agent object. """ from strands import Agent - # Build system prompt from crew description and agent backstories - system_prompt = self._build_system_prompt(crew_config) + # Build system prompt from fabric agent description and agent backstories. + system_prompt = self._build_system_prompt(fabric_agent_config) # Collect tools from tasks - tools = self._collect_tools(crew_config) + tools = self._collect_tools(fabric_agent_config) - # Get LLM/model configuration from crew config - llm_config = crew_config.get("llm", {}) + # Get LLM/model configuration from fabric agent config. + llm_config = fabric_agent_config.get("llm", {}) model_provider = self._get_model_provider(llm_config) agent_kwargs = { @@ -67,7 +67,7 @@ def _get_model_provider(self, llm_config: dict | str | None) -> str | None: """Get Strands-compatible model provider from LLM config. Args: - llm_config: LLM configuration from crew config. + llm_config: LLM configuration from fabric agent config. Returns: Model provider string for Strands, or None for default. @@ -81,11 +81,11 @@ def _get_model_provider(self, llm_config: dict | str | None) -> str | None: # Extract model from config dict return llm_config.get("model") - def run(self, crew: Any, inputs: dict[str, Any]) -> str: + def run(self, fabric_agent: Any, inputs: dict[str, Any]) -> str: """Execute the Strands agent. Args: - crew: Strands Agent object. + fabric_agent: Strands Agent object. inputs: Inputs for the agent. Returns: @@ -94,7 +94,7 @@ def run(self, crew: Any, inputs: dict[str, Any]) -> str: # Convert inputs to prompt prompt = inputs.get("input", inputs.get("task", str(inputs))) - result = crew(prompt) + result = fabric_agent(prompt) return str(result) @@ -140,26 +140,26 @@ def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: "agent": agent, } - def _build_system_prompt(self, crew_config: dict[str, Any]) -> str: - """Build a comprehensive system prompt from crew config. + def _build_system_prompt(self, fabric_agent_config: dict[str, Any]) -> str: + """Build a comprehensive system prompt from fabric agent config. Combines all agent roles, goals, and backstories into a single capable system prompt. Args: - crew_config: Crew configuration. + fabric_agent_config: Fabric agent configuration. Returns: System prompt string. """ parts = [] - # Add crew description - if crew_config.get("description"): - parts.append(f"# Your Purpose\n{crew_config['description']}") + # Add fabric agent description. + if fabric_agent_config.get("description"): + parts.append(f"# Your Purpose\n{fabric_agent_config['description']}") # Add agent capabilities - agents = crew_config.get("agents", {}) + agents = fabric_agent_config.get("agents", {}) if agents: parts.append("\n# Your Capabilities") for agent_name, agent_cfg in agents.items(): @@ -170,7 +170,7 @@ def _build_system_prompt(self, crew_config: dict[str, Any]) -> str: parts.append(f"Goal: {goal}") # Add task context - tasks = crew_config.get("tasks", {}) + tasks = fabric_agent_config.get("tasks", {}) if tasks: parts.append("\n# Tasks You Can Perform") for task_name, task_cfg in tasks.items(): @@ -184,9 +184,9 @@ def _build_system_prompt(self, crew_config: dict[str, Any]) -> str: return "\n".join(parts) - def _collect_tools(self, crew_config: dict[str, Any]) -> list: - """Collect tools declared by agents in crew configuration.""" + def _collect_tools(self, fabric_agent_config: dict[str, Any]) -> list: + """Collect tools declared by agents in fabric agent configuration.""" tool_names: list[str] = [] - for agent_cfg in crew_config.get("agents", {}).values(): + for agent_cfg in fabric_agent_config.get("agents", {}).values(): tool_names.extend(agent_cfg.get("tools", [])) return resolve_strands_tools(tool_names) diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/__init__.py b/packages/agentic-fabric/src/agentic_fabric/tools/__init__.py index e32ba32..19005ea 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/__init__.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/__init__.py @@ -56,12 +56,8 @@ def get_all_tools() -> list[Any]: with suppress(ImportError): tools.extend(get_scraping_tools()) - try: - from mesh_toolkit.agent_tools.crewai import get_tools as get_meshy_tools - - tools.extend(get_meshy_tools()) - except ImportError: - pass + with suppress(AttributeError, ImportError, RuntimeError): + tools.extend(__getattr__("vendor_capability_tools")(include_unavailable=False)) return tools diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/meshy_mcp.py b/packages/agentic-fabric/src/agentic_fabric/tools/meshy_mcp.py new file mode 100644 index 0000000..1718cf5 --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/tools/meshy_mcp.py @@ -0,0 +1,203 @@ +"""MCP adapter for vendor-fabric Meshy capabilities. + +Meshy provider calls stay in vendor-fabric. This agentic-fabric module only +decides how those provider capabilities become MCP tools. +""" + +from __future__ import annotations + +import asyncio +import json + +from collections.abc import Callable, Iterable, Mapping +from typing import Any, cast + + +MCP_INSTALL_MESSAGE = "MCP SDK not installed. Install with: pip install agentic-fabric[mcp]" +VENDOR_INSTALL_MESSAGE = ( + "vendor-fabric[meshy] is required for Meshy MCP adapters. Install it in the same environment." +) + + +def _require_meshy_tool_definitions() -> list[dict[str, Any]]: + """Load Meshy capability metadata from vendor-fabric lazily.""" + try: + from vendor_fabric.meshy.tools import TOOL_DEFINITIONS + except ImportError as exc: + raise ImportError(VENDOR_INSTALL_MESSAGE) from exc + return list(TOOL_DEFINITIONS) + + +def _schema_for_definition(definition: Mapping[str, Any]) -> dict[str, Any]: + """Return an MCP input schema for one Meshy capability definition.""" + schema = definition.get("schema") + if schema is None: + return {"type": "object", "properties": {}, "required": []} + + model_json_schema = getattr(schema, "model_json_schema", None) + if callable(model_json_schema): + generated = model_json_schema() + return { + "type": "object", + "properties": generated.get("properties", {}), + "required": generated.get("required", []), + } + + return {"type": "object", "properties": {}, "required": []} + + +def _create_mcp_tools() -> list[tuple[Any, Callable[..., Any]]]: + """Create MCP tool definitions from Meshy provider capabilities.""" + try: + from mcp.types import Tool + except ImportError as exc: + raise ImportError(MCP_INSTALL_MESSAGE) from exc + + mcp_tools: list[tuple[Any, Callable[..., Any]]] = [] + for definition in _require_meshy_tool_definitions(): + func = definition["func"] + tool = Tool( + name=str(definition["name"]), + description=str(definition.get("description") or definition["name"]), + inputSchema=_schema_for_definition(definition), + ) + mcp_tools.append((tool, func)) + + return mcp_tools + + +def _to_builtin(value: Any) -> Any: + try: + from extended_data.containers import to_builtin + except ImportError: + if hasattr(value, "model_dump"): + return value.model_dump() + if isinstance(value, Mapping): + return {key: _to_builtin(item) for key, item in value.items()} + if isinstance(value, Iterable) and not isinstance(value, str | bytes | bytearray): + return [_to_builtin(item) for item in value] + return value + return to_builtin(value) + + +def _redact_sensitive_data(value: Any) -> Any: + try: + from extended_data.primitives.redaction import redact_sensitive_data + except ImportError: + return value + return redact_sensitive_data(value) + + +def _redact_sensitive_text(value: object, *, values: Iterable[Any] | None = None) -> str: + try: + from extended_data.primitives.redaction import redact_sensitive_text + except ImportError: + return str(value) + return redact_sensitive_text(value, values=values) + + +def _jsonable_tool_result(result: Any) -> Any: + """Lower Meshy tool results to JSON-compatible redacted data.""" + if hasattr(result, "model_dump"): + result = result.model_dump() + elif isinstance(result, Iterable) and not isinstance(result, str | bytes | bytearray | Mapping): + result = [item.model_dump() if hasattr(item, "model_dump") else item for item in result] + result = _to_builtin(result) + if isinstance(result, set | frozenset): + result = [_to_builtin(item) for item in result] + return _redact_sensitive_data(result) + + +def _tool_error_payload(error: object, *, values: Iterable[Any] | None = None) -> dict[str, str]: + """Return an MCP-safe error payload without raw secret values.""" + return {"error": _redact_sensitive_text(error, values=values)} + + +def _tool_payload_text(payload: Any) -> str: + """Return a serialized MCP text payload.""" + try: + from extended_data.io import wrap_raw_data_for_export + except ImportError: + return json.dumps(payload, indent=2, default=str) + return wrap_raw_data_for_export(payload, allow_encoding="json", indent_2=True) + + +def _tool_result_text(result: Any) -> str: + """Return a serialized Meshy MCP result.""" + return _tool_payload_text(_jsonable_tool_result(result)) + + +def create_server() -> Any: + """Create an MCP server with Meshy provider capabilities.""" + try: + from mcp.server import Server + except ImportError as exc: + raise ImportError(MCP_INSTALL_MESSAGE) from exc + + server = Server("meshy-ai") + mcp_tools = _create_mcp_tools() + tool_handlers = {tool.name: func for tool, func in mcp_tools} + tool_list = [tool for tool, _ in mcp_tools] + + tool_decorator = cast(Callable[[], Callable[[Callable[..., Any]], Callable[..., Any]]], server.list_tools) + call_decorator = cast(Callable[[], Callable[[Callable[..., Any]], Callable[..., Any]]], server.call_tool) + + @tool_decorator() + async def list_tools() -> list[Any]: + return tool_list + + @call_decorator() + async def call_tool(name: str, arguments: dict[str, Any] | None) -> list[Any]: + from mcp.types import TextContent + + tool_arguments = arguments or {} + handler = tool_handlers.get(name) + if not handler: + return [ + TextContent( + type="text", + text=_tool_payload_text(_tool_error_payload(f"Unknown tool: {name}")), + ) + ] + + try: + result = handler(**tool_arguments) + return [TextContent(type="text", text=_tool_result_text(result))] + except Exception as exc: + return [ + TextContent( + type="text", + text=_tool_payload_text(_tool_error_payload(exc, values=tool_arguments.values())), + ) + ] + + return server + + +def run_server(server: Any | None = None) -> None: + """Run the Meshy MCP adapter over stdio.""" + try: + from mcp.server.stdio import stdio_server + except ImportError as exc: + raise ImportError(MCP_INSTALL_MESSAGE) from exc + + if server is None: + server = create_server() + + async def main() -> None: + async with stdio_server() as (read_stream, write_stream): + await server.run(read_stream, write_stream, server.create_initialization_options()) + + asyncio.run(main()) + + +def main() -> None: + """Entry point for the Meshy MCP adapter.""" + run_server() + + +if __name__ == "__main__": # pragma: no cover + main() + + +__all__ = ["create_server", "main", "run_server"] diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/registry.py b/packages/agentic-fabric/src/agentic_fabric/tools/registry.py index e4e4582..87bf507 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/registry.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/registry.py @@ -1,4 +1,4 @@ -"""Tool resolution helpers for configured crew tools. +"""Tool resolution helpers for configured fabric agent tools. This module turns YAML-declared tool names into instantiated tool objects without forcing every optional dependency to import at module load time. @@ -143,7 +143,7 @@ def resolve_tool(tool_name: str) -> Any | None: def resolve_tools(tool_names: list[str]) -> list[Any]: """Resolve configured tool names to instantiated tool objects. - Unknown or unavailable tools are skipped with a warning so that crews can + Unknown or unavailable tools are skipped with a warning so that fabric agents can continue to run with the capabilities available in the current environment. """ resolved: list[Any] = [] diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/vendor_mcp.py b/packages/agentic-fabric/src/agentic_fabric/tools/vendor_mcp.py new file mode 100644 index 0000000..7f7e1df --- /dev/null +++ b/packages/agentic-fabric/src/agentic_fabric/tools/vendor_mcp.py @@ -0,0 +1,330 @@ +"""MCP adapters for vendor-fabric provider capabilities. + +This module lives in agentic-fabric because MCP is a runtime-visible tool +transport. The provider implementation and capability metadata stay in +vendor-fabric and are imported lazily only when the MCP server is created. +""" + +from __future__ import annotations + +import asyncio +import builtins +import inspect +import json + +from collections.abc import Callable, Iterable, Mapping +from typing import Any, cast, get_origin, get_type_hints + + +MCP_INSTALL_MESSAGE = "MCP SDK not installed. Install with: pip install agentic-fabric[mcp]" +VENDOR_INSTALL_MESSAGE = ( + "vendor-fabric is required for vendor MCP adapters. Install vendor-fabric in the same environment." +) + + +def _require_vendor_fabric() -> tuple[Any, Callable[..., Any]]: + """Load vendor-fabric registry and surface helpers lazily.""" + try: + from vendor_fabric import registry + from vendor_fabric.surface import connector_data_methods + except ImportError as exc: + raise ImportError(VENDOR_INSTALL_MESSAGE) from exc + return registry, connector_data_methods + + +def _get_method_schema(method: Callable[..., Any]) -> dict[str, Any]: + """Generate a JSON schema from a provider method signature.""" + sig = inspect.signature(method) + try: + type_hints = get_type_hints(method) + except Exception: + type_hints = {} + properties: dict[str, dict[str, Any]] = {} + required: list[str] = [] + + for name, param in sig.parameters.items(): + if name in {"self", "cls"}: + continue + + prop: dict[str, Any] = {"type": "string"} + ann = type_hints.get(name, param.annotation) + if ann != inspect.Parameter.empty: + if ann is int: + prop = {"type": "integer"} + elif ann is float: + prop = {"type": "number"} + elif ann is bool: + prop = {"type": "boolean"} + elif ann is list or get_origin(ann) is list: + prop = {"type": "array"} + elif ann is dict or get_origin(ann) is dict: + prop = {"type": "object"} + + if method.__doc__: + for line in method.__doc__.split("\n"): + if f"{name}:" in line.lower(): + prop["description"] = line.split(":", 1)[-1].strip() + break + + if param.default != inspect.Parameter.empty: + prop["default"] = param.default + else: + required.append(name) + + properties[name] = prop + + return {"type": "object", "properties": properties, "required": required} + + +def _get_public_methods( + connector_class: builtins.type[Any], + connector_data_methods: Callable[[builtins.type[Any]], list[tuple[str, Callable[..., Any]]]], +) -> list[tuple[str, Callable[..., Any]]]: + """Get public provider data methods for MCP exposure.""" + return connector_data_methods(connector_class) + + +def _connector_classes(registry: Any) -> Mapping[str, builtins.type[Any]]: + """Return connector classes for runtime method exposure. + + vendor-fabric currently keeps class discovery internal. The MCP adapter + isolates that private call so catalog tools still work if the public + surface changes before a stable method-list API is published. + """ + list_connector_classes = getattr(registry, "_list_connector_classes", None) + if not callable(list_connector_classes): + return {} + return cast(Mapping[str, builtins.type[Any]], list_connector_classes()) + + +def _catalog_tool_definitions(registry: Any) -> dict[str, dict[str, Any]]: + """Build credential-free vendor catalog MCP tools.""" + include_unavailable_schema: dict[str, Any] = { + "type": "object", + "properties": {"include_unavailable": {"type": "boolean", "default": True}}, + "required": [], + } + empty_schema: dict[str, Any] = {"type": "object", "properties": {}, "required": []} + name_schema: dict[str, Any] = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "include_unavailable": {"type": "boolean", "default": True}, + }, + "required": ["name"], + } + category_schema: dict[str, Any] = { + "type": "object", + "properties": { + "category": {"type": "string"}, + "include_unavailable": {"type": "boolean", "default": True}, + }, + "required": ["category"], + } + capability_schema: dict[str, Any] = { + "type": "object", + "properties": { + "capability": {"type": "string"}, + "include_unavailable": {"type": "boolean", "default": True}, + }, + "required": ["capability"], + } + + return { + "fabric_vendors_list": { + "description": "List vendors registered in the fabric catalog.", + "parameters": include_unavailable_schema, + "handler": registry.list_connectors, + }, + "fabric_vendors_list_available": { + "description": "List vendors currently available in this environment.", + "parameters": empty_schema, + "handler": registry.list_available_connectors, + }, + "fabric_vendors_list_info": { + "description": "List vendor catalog metadata.", + "parameters": include_unavailable_schema, + "handler": registry.list_connector_info, + }, + "fabric_vendor_get_info": { + "description": "Get catalog metadata for one vendor.", + "parameters": name_schema, + "handler": registry.get_connector_info, + }, + "fabric_vendors_list_categories": { + "description": "List vendor categories in the fabric catalog.", + "parameters": include_unavailable_schema, + "handler": registry.list_connector_categories, + }, + "fabric_vendors_list_capabilities": { + "description": "List vendor capabilities in the fabric catalog.", + "parameters": include_unavailable_schema, + "handler": registry.list_connector_capabilities, + }, + "fabric_vendors_list_by_category": { + "description": "List vendor catalog entries for a category.", + "parameters": category_schema, + "handler": registry.list_connectors_by_category, + }, + "fabric_vendors_list_by_capability": { + "description": "List vendor catalog entries for a capability.", + "parameters": capability_schema, + "handler": registry.list_connectors_by_capability, + }, + } + + +def _to_builtin(value: Any) -> Any: + try: + from extended_data.containers import to_builtin + except ImportError: + if hasattr(value, "model_dump"): + return value.model_dump() + if isinstance(value, Mapping): + return {key: _to_builtin(item) for key, item in value.items()} + if isinstance(value, Iterable) and not isinstance(value, str | bytes | bytearray): + return [_to_builtin(item) for item in value] + return value + return to_builtin(value) + + +def _redact_sensitive_data(value: Any) -> Any: + try: + from extended_data.primitives.redaction import redact_sensitive_data + except ImportError: + return value + return redact_sensitive_data(value) + + +def _redact_sensitive_text(value: object, *, values: Iterable[Any] | None = None) -> str: + try: + from extended_data.primitives.redaction import redact_sensitive_text + except ImportError: + return str(value) + return redact_sensitive_text(value, values=values) + + +def _jsonable_tool_result(result: Any) -> Any: + """Lower provider tool results to JSON-compatible redacted data.""" + if hasattr(result, "model_dump"): + result = result.model_dump() + elif isinstance(result, Iterable) and not isinstance(result, str | bytes | bytearray | Mapping): + result = [item.model_dump() if hasattr(item, "model_dump") else item for item in result] + result = _to_builtin(result) + if isinstance(result, set | frozenset): + result = [_to_builtin(item) for item in result] + return _redact_sensitive_data(result) + + +def _tool_error_text(error: Exception, values: Iterable[Any] | None = None) -> str: + """Return an MCP-safe error string without raw secret values.""" + return f"Error: {type(error).__name__}: {_redact_sensitive_text(error, values=values)}" + + +def _unknown_tool_text(name: str) -> str: + """Return an MCP-safe unknown-tool diagnostic.""" + return f"Unknown tool: {_redact_sensitive_text(name)}" + + +def _tool_result_text(result: Any) -> str: + """Return a serialized MCP tool result.""" + payload = _jsonable_tool_result(result) + try: + from extended_data.io import wrap_raw_data_for_export + except ImportError: + return json.dumps(payload, indent=2, default=str) + return wrap_raw_data_for_export(payload, allow_encoding="json", indent_2=True, default=str) + + +def create_server() -> Any: + """Create an MCP server exposing vendor-fabric provider capabilities.""" + try: + from mcp.server import Server + from mcp.types import TextContent, Tool + except ImportError as exc: + raise ImportError(MCP_INSTALL_MESSAGE) from exc + + registry, connector_data_methods = _require_vendor_fabric() + server = Server("vendor-fabric") + tools: dict[str, dict[str, Any]] = _catalog_tool_definitions(registry) + + for connector_name, connector_class in _connector_classes(registry).items(): + for method_name, method in _get_public_methods(connector_class, connector_data_methods): + if method_name in {"close", "request", "get_input", "register_tool"}: + continue + + description = method.__doc__.split("\n")[0].strip() if method.__doc__ else "" + tools[f"{connector_name}_{method_name}"] = { + "connector": connector_name, + "method": method_name, + "description": description or f"{connector_name}.{method_name}()", + "parameters": _get_method_schema(method), + } + + tool_decorator = cast(Callable[[], Callable[[Callable[..., Any]], Callable[..., Any]]], server.list_tools) + call_decorator = cast(Callable[[], Callable[[Callable[..., Any]], Callable[..., Any]]], server.call_tool) + + @tool_decorator() + async def list_tools() -> list[Tool]: + return [ + Tool(name=name, description=tool["description"], inputSchema=tool["parameters"]) + for name, tool in tools.items() + ] + + @call_decorator() + async def call_tool(name: str, arguments: dict[str, Any] | None) -> list[TextContent]: + tool_arguments = arguments or {} + if name not in tools: + return [TextContent(type="text", text=_unknown_tool_text(name))] + + tool = tools[name] + handler = tool.get("handler") + if callable(handler): + try: + result = handler(**tool_arguments) + if inspect.iscoroutine(result): + result = await result + return [TextContent(type="text", text=_tool_result_text(result))] + except Exception as exc: + return [TextContent(type="text", text=_tool_error_text(exc, tool_arguments.values()))] + + try: + connector = registry.get_connector(tool["connector"]) + method = getattr(connector, tool["method"]) + result = method(**tool_arguments) + if inspect.iscoroutine(result): + result = await result + return [TextContent(type="text", text=_tool_result_text(result))] + except Exception as exc: + return [TextContent(type="text", text=_tool_error_text(exc, tool_arguments.values()))] + + return server + + +def run_server(server: Any | None = None) -> None: + """Run the vendor MCP adapter over stdio.""" + try: + from mcp.server.stdio import stdio_server + except ImportError as exc: + raise ImportError(MCP_INSTALL_MESSAGE) from exc + + if server is None: + server = create_server() + + async def run() -> None: + async with stdio_server() as (read_stream, write_stream): + await server.run(read_stream, write_stream, server.create_initialization_options()) + + asyncio.run(run()) + + +def main() -> None: + """Entry point for the vendor MCP adapter.""" + run_server() + + +if __name__ == "__main__": # pragma: no cover + main() + + +__all__ = ["create_server", "main", "run_server"] diff --git a/packages/agentic-fabric/tests/_crew_mocker.py b/packages/agentic-fabric/tests/_fabric_mocker.py similarity index 92% rename from packages/agentic-fabric/tests/_crew_mocker.py rename to packages/agentic-fabric/tests/_fabric_mocker.py index c1cf5ad..6a753b6 100644 --- a/packages/agentic-fabric/tests/_crew_mocker.py +++ b/packages/agentic-fabric/tests/_fabric_mocker.py @@ -48,12 +48,12 @@ # ============================================================================= -# CrewMocker Class - Enhanced mocker for agentic-fabric testing +# FabricMocker Class - Enhanced mocker for agentic-fabric testing # ============================================================================= @dataclass -class CrewMocker: +class FabricMocker: """Enhanced mocker specifically for agentic-fabric testing. This class wraps pytest-mock's MockerFixture and provides convenience @@ -64,10 +64,10 @@ class CrewMocker: mocked_modules: Dictionary of currently mocked framework modules. Example: - def test_crewai_runner(crew_mocker): + def test_crewai_runner(fabric_mocker): # Mock CrewAI components - mock_agent = crew_mocker.mock_crewai_agent() - mock_crew = crew_mocker.mock_crewai_crew() + mock_agent = fabric_mocker.mock_crewai_agent() + mock_crew = fabric_mocker.mock_crewai_crew() # Test runner behavior from agentic_fabric.runners.crewai_runner import CrewAIRunner @@ -389,29 +389,29 @@ def patch_discover_packages(self, packages: dict[str, Any] | None = None) -> Any mock.return_value = packages or {} return mock - def patch_get_crew_config(self, config: dict[str, Any] | None = None) -> Any: - """Patch agentic_fabric.core.discovery.get_crew_config. + def patch_get_fabric_agent_config(self, config: dict[str, Any] | None = None) -> Any: + """Patch agentic_fabric.core.discovery.get_fabric_agent_config. Args: config: Config to return. If None, returns minimal valid config. Returns: - The mock for get_crew_config. + The mock for get_fabric_agent_config. """ - mock = self.mocker.patch("agentic_fabric.core.discovery.get_crew_config") + mock = self.mocker.patch("agentic_fabric.core.discovery.get_fabric_agent_config") mock.return_value = config or {"name": "test", "agents": {}, "tasks": {}} return mock - def patch_run_crew_auto(self, result: str = "Test result") -> Any: - """Patch agentic_fabric.core.decomposer.run_crew_auto. + def patch_run_fabric_agent_auto(self, result: str = "Test result") -> Any: + """Patch agentic_fabric.core.decomposer.run_fabric_agent_auto. Args: - result: Result string to return from run_crew_auto. + result: Result string to return from run_fabric_agent_auto. Returns: - The mock for run_crew_auto. + The mock for run_fabric_agent_auto. """ - mock = self.mocker.patch("agentic_fabric.core.decomposer.run_crew_auto") + mock = self.mocker.patch("agentic_fabric.core.decomposer.run_fabric_agent_auto") mock.return_value = result return mock @@ -422,27 +422,27 @@ def patch_run_crew_auto(self, result: str = "Test result") -> Any: @pytest.fixture -def crew_mocker(mocker: MockerFixture) -> Generator[CrewMocker, None, None]: - """Fixture providing CrewMocker for agentic-fabric testing. +def fabric_mocker(mocker: MockerFixture) -> Generator[FabricMocker, None, None]: + """Fixture providing FabricMocker for agentic-fabric testing. This fixture builds on pytest-mock's mocker fixture and provides specialized methods for mocking AI framework components. Yields: - CrewMocker instance with access to all mocking utilities. + FabricMocker instance with access to all mocking utilities. Example: - def test_my_runner(crew_mocker): + def test_my_runner(fabric_mocker): # Mock CrewAI - crew_mocker.mock_crewai() - mock_llm = crew_mocker.patch_get_llm() + fabric_mocker.mock_crewai() + mock_llm = fabric_mocker.patch_get_llm() # Test code from agentic_fabric.runners.crewai_runner import CrewAIRunner runner = CrewAIRunner() ... """ - cm = CrewMocker(mocker=mocker) + cm = FabricMocker(mocker=mocker) yield cm cm.restore_modules() @@ -464,7 +464,7 @@ def test_crewai_runner(mock_frameworks): runner = CrewAIRunner() # Test runner behavior with mocked framework """ - cm = CrewMocker(mocker=mocker) + cm = FabricMocker(mocker=mocker) mocks = cm.mock_all_frameworks() yield mocks cm.restore_modules() @@ -477,7 +477,7 @@ def mock_crewai(mocker: MockerFixture) -> Generator[dict[str, Any], None, None]: Yields: Dictionary with CrewAI mock objects. """ - cm = CrewMocker(mocker=mocker) + cm = FabricMocker(mocker=mocker) mocks = cm.mock_crewai() yield mocks cm.restore_modules() @@ -490,7 +490,7 @@ def mock_langgraph(mocker: MockerFixture) -> Generator[dict[str, Any], None, Non Yields: Dictionary with LangGraph mock objects. """ - cm = CrewMocker(mocker=mocker) + cm = FabricMocker(mocker=mocker) mocks = cm.mock_langgraph() yield mocks cm.restore_modules() @@ -503,7 +503,7 @@ def mock_strands(mocker: MockerFixture) -> Generator[dict[str, Any], None, None] Yields: Dictionary with Strands mock objects. """ - cm = CrewMocker(mocker=mocker) + cm = FabricMocker(mocker=mocker) mocks = cm.mock_strands() yield mocks cm.restore_modules() diff --git a/packages/agentic-fabric/tests/conftest.py b/packages/agentic-fabric/tests/conftest.py index 464bdb0..5e5ca80 100644 --- a/packages/agentic-fabric/tests/conftest.py +++ b/packages/agentic-fabric/tests/conftest.py @@ -11,7 +11,7 @@ import pytest -from tests._crew_mocker import crew_mocker, mock_crewai, mock_frameworks, mock_langgraph, mock_strands # noqa: F401 +from tests._fabric_mocker import fabric_mocker, mock_crewai, mock_frameworks, mock_langgraph, mock_strands # noqa: F401 def pytest_addoption(parser: Any) -> None: @@ -88,26 +88,26 @@ def temp_workspace(tmp_path: Path) -> Path: manifest.write_text(""" name: sample description: Test package -crews: - test_crew: - description: A test crew - agents: crews/test_crew/agents.yaml - tasks: crews/test_crew/tasks.yaml +fabric_agents: + test_fabric_agent: + description: A test fabric agent + agents: fabric_agents/test_fabric_agent/agents.yaml + tasks: fabric_agents/test_fabric_agent/tasks.yaml """) - # Create crews directory - crews_dir = crewai_dir / "crews" / "test_crew" - crews_dir.mkdir(parents=True) + # Create fabric_agents directory + fabric_agents_dir = crewai_dir / "fabric_agents" / "test_fabric_agent" + fabric_agents_dir.mkdir(parents=True) # Create minimal agent and task configs - (crews_dir / "agents.yaml").write_text(""" + (fabric_agents_dir / "agents.yaml").write_text(""" test_agent: role: Test Agent goal: Test goal backstory: Test backstory """) - (crews_dir / "tasks.yaml").write_text(""" + (fabric_agents_dir / "tasks.yaml").write_text(""" test_task: description: Test task description expected_output: Test output @@ -118,8 +118,8 @@ def temp_workspace(tmp_path: Path) -> Path: @pytest.fixture -def mock_crew() -> MagicMock: - """Create a mock crew result.""" +def mock_fabric_agent_result() -> MagicMock: + """Create a mock fabric agent result.""" result = MagicMock() result.raw = {"output": "test output", "success": True} return result @@ -142,12 +142,12 @@ def check_aws_credentials() -> None: @pytest.fixture -def temp_crew_dir(tmp_path: Path) -> Path: - """Create a temporary crew directory with minimal structure.""" - crew_dir = tmp_path / "test_package" / ".crewai" - crew_dir.mkdir(parents=True) - (crew_dir / "crews").mkdir() - return crew_dir +def temp_crewai_dir(tmp_path: Path) -> Path: + """Create a temporary CrewAI config directory with minimal structure.""" + crewai_dir = tmp_path / "test_package" / ".crewai" + crewai_dir.mkdir(parents=True) + (crewai_dir / "fabric_agents").mkdir() + return crewai_dir @pytest.fixture @@ -170,14 +170,14 @@ def simple_task_config() -> dict[str, Any]: @pytest.fixture -def simple_crew_config( +def simple_fabric_agent_config( simple_agent_config: dict[str, Any], simple_task_config: dict[str, Any], ) -> dict[str, Any]: - """Get a simple crew configuration for runner tests.""" + """Get a simple fabric agent configuration for runner tests.""" return { - "name": "test_crew", - "description": "A simple test crew", + "name": "test_fabric_agent", + "description": "A simple test fabric agent", "agents": {"test_agent": simple_agent_config}, "tasks": { "test_task": { @@ -190,11 +190,11 @@ def simple_crew_config( @pytest.fixture -def multi_agent_crew_config() -> dict[str, Any]: - """Get a multi-agent crew configuration for runner tests.""" +def multi_agent_fabric_agent_config() -> dict[str, Any]: + """Get a multi-agent fabric agent configuration for runner tests.""" return { - "name": "multi_agent_crew", - "description": "A crew with multiple collaborating agents", + "name": "multi_agent_fabric_agent", + "description": "A fabric agent with multiple collaborating agents", "agents": { "researcher": { "role": "Researcher", @@ -225,8 +225,8 @@ def multi_agent_crew_config() -> dict[str, Any]: @pytest.fixture -def crew_with_knowledge(tmp_path: Path) -> dict[str, Any]: - """Get a crew configuration with a local knowledge source.""" +def fabric_agent_with_knowledge(tmp_path: Path) -> dict[str, Any]: + """Get a fabric agent configuration with a local knowledge source.""" knowledge_dir = tmp_path / "knowledge" knowledge_dir.mkdir() (knowledge_dir / "test_info.md").write_text( @@ -239,8 +239,8 @@ def crew_with_knowledge(tmp_path: Path) -> dict[str, Any]: ) return { - "name": "knowledge_crew", - "description": "A crew with knowledge sources", + "name": "knowledge_fabric_agent", + "description": "A fabric agent with knowledge sources", "agents": { "knowledgeable_agent": { "role": "Knowledge Expert", diff --git a/packages/agentic-fabric/tests/e2e/README.md b/packages/agentic-fabric/tests/e2e/README.md index 1da12d0..cf1d444 100644 --- a/packages/agentic-fabric/tests/e2e/README.md +++ b/packages/agentic-fabric/tests/e2e/README.md @@ -6,7 +6,7 @@ This directory contains end-to-end tests for framework runners (CrewAI, LangGrap E2E tests verify that each framework runner works correctly with: - Real LLM API calls -- Complete crew execution flows +- Complete fabric_agent execution flows - Multi-agent collaboration - Knowledge source integration - Tool usage @@ -71,7 +71,7 @@ When configuring Strands tests with Bedrock, use the official model IDs from [Cl Example configuration: ```python -crew_config = { +fabric_agent_config = { "llm": { "provider": "bedrock", "model": "anthropic.claude-haiku-4-5-20251001-v1:0", @@ -94,9 +94,9 @@ tests/e2e/ ### CrewAI (`test_crewai_e2e.py`) -- ✅ Simple single-agent crew execution +- ✅ Simple single-agent fabric_agent execution - ✅ Build and run convenience method -- ✅ Multi-agent crew collaboration +- ✅ Multi-agent fabric_agent collaboration - ✅ Knowledge source integration - ✅ Tool usage with agents @@ -166,13 +166,13 @@ Example: @pytest.mark.crewai def test_my_feature( check_api_key: None, - simple_crew_config: dict[str, Any], + simple_fabric_agent_config: dict[str, Any], ) -> None: """Test my new feature.""" from agentic_fabric.runners.crewai_runner import CrewAIRunner runner = CrewAIRunner() - result = runner.build_and_run(simple_crew_config, {"input": "test"}) + result = runner.build_and_run(simple_fabric_agent_config, {"input": "test"}) assert result is not None ``` diff --git a/packages/agentic-fabric/tests/e2e/__init__.py b/packages/agentic-fabric/tests/e2e/__init__.py index af68b0d..e64e48f 100644 --- a/packages/agentic-fabric/tests/e2e/__init__.py +++ b/packages/agentic-fabric/tests/e2e/__init__.py @@ -1,6 +1,6 @@ """End-to-end tests for framework runners. -These tests verify that each framework runner works with real crews and LLM calls. +These tests verify that each framework runner works with real fabric_agents and LLM calls. They are disabled by default and require the --e2e flag to run. """ diff --git a/packages/agentic-fabric/tests/e2e/conftest.py b/packages/agentic-fabric/tests/e2e/conftest.py index b27fd66..0014ee6 100644 --- a/packages/agentic-fabric/tests/e2e/conftest.py +++ b/packages/agentic-fabric/tests/e2e/conftest.py @@ -11,10 +11,10 @@ # - check_aws_credentials # - simple_agent_config # - simple_task_config -# - simple_crew_config -# - multi_agent_crew_config -# - crew_with_knowledge -# - temp_crew_dir +# - simple_fabric_agent_config +# - multi_agent_fabric_agent_config +# - fabric_agent_with_knowledge +# - temp_crewai_dir # # CLI options: # - --e2e: Enable E2E tests diff --git a/packages/agentic-fabric/tests/e2e/test_crewai_e2e.py b/packages/agentic-fabric/tests/e2e/test_crewai_e2e.py index fb9877d..c77d41c 100644 --- a/packages/agentic-fabric/tests/e2e/test_crewai_e2e.py +++ b/packages/agentic-fabric/tests/e2e/test_crewai_e2e.py @@ -31,30 +31,30 @@ class TestCrewAISimpleExecution: - """Test simple crew execution with CrewAI.""" + """Test simple fabric_agent execution with CrewAI.""" def test_simple_crew_execution( self, check_api_key: None, - simple_crew_config: dict[str, Any], + simple_fabric_agent_config: dict[str, Any], ) -> None: - """Test execution of a simple single-agent, single-task crew. + """Test execution of a simple single-agent, single-task fabric_agent. Args: check_api_key: Fixture to check for required API keys. - simple_crew_config: Simple crew configuration fixture. + simple_fabric_agent_config: Simple fabric_agent configuration fixture. """ from agentic_fabric.runners.crewai_runner import CrewAIRunner runner = CrewAIRunner() - # Build the crew - crew = runner.build_crew(simple_crew_config) - assert crew is not None + # Build the fabric_agent + fabric_agent = runner.build_fabric_agent(simple_fabric_agent_config) + assert fabric_agent is not None - # Run the crew with a simple input + # Run the fabric_agent with a simple input inputs = {"input": "What is 2 + 2?"} - result = runner.run(crew, inputs) + result = runner.run(fabric_agent, inputs) # Verify we got a response assert result is not None @@ -63,16 +63,16 @@ def test_simple_crew_execution( # The answer should contain '4' somewhere assert "4" in result - def test_crew_build_and_run( + def test_fabric_agent_build_and_run( self, check_api_key: None, - simple_crew_config: dict[str, Any], + simple_fabric_agent_config: dict[str, Any], ) -> None: """Test the convenience build_and_run method. Args: check_api_key: Fixture to check for required API keys. - simple_crew_config: Simple crew configuration fixture. + simple_fabric_agent_config: Simple fabric_agent configuration fixture. """ from agentic_fabric.runners.crewai_runner import CrewAIRunner @@ -80,7 +80,7 @@ def test_crew_build_and_run( # Use build_and_run convenience method inputs = {"input": "What is the capital of France?"} - result = runner.build_and_run(simple_crew_config, inputs) + result = runner.build_and_run(simple_fabric_agent_config, inputs) # Verify we got a response about Paris assert result is not None @@ -92,24 +92,24 @@ def test_crew_build_and_run( class TestCrewAIMultiAgent: """Test multi-agent collaboration with CrewAI.""" - def test_multi_agent_crew( + def test_multi_agent_fabric_agent( self, check_api_key: None, - multi_agent_crew_config: dict[str, Any], + multi_agent_fabric_agent_config: dict[str, Any], ) -> None: - """Test multi-agent crew with sequential task execution. + """Test multi-agent fabric_agent with sequential task execution. Args: check_api_key: Fixture to check for required API keys. - multi_agent_crew_config: Multi-agent crew configuration fixture. + multi_agent_fabric_agent_config: Multi-agent fabric_agent configuration fixture. """ from agentic_fabric.runners.crewai_runner import CrewAIRunner runner = CrewAIRunner() - # Build and run multi-agent crew + # Build and run multi-agent fabric_agent inputs = {"input": "Tell me about Python programming"} - result = runner.build_and_run(multi_agent_crew_config, inputs) + result = runner.build_and_run(multi_agent_fabric_agent_config, inputs) # Verify we got a result from the collaborative effort assert result is not None @@ -125,23 +125,23 @@ class TestCrewAIKnowledge: def test_knowledge_source_integration( self, check_api_key: None, - crew_with_knowledge: dict[str, Any], + fabric_agent_with_knowledge: dict[str, Any], ) -> None: - """Test crew with knowledge sources loaded from files. + """Test fabric agent with knowledge sources loaded from files. Args: check_api_key: Fixture to check for required API keys. - crew_with_knowledge: Crew config with knowledge sources fixture. + fabric_agent_with_knowledge: Fabric agent config with knowledge sources fixture. """ from agentic_fabric.runners.crewai_runner import CrewAIRunner runner = CrewAIRunner() - # Build and run crew with knowledge + # Build and run fabric agent with knowledge. inputs = {"question": "What color is mentioned in the knowledge base?"} - result = runner.build_and_run(crew_with_knowledge, inputs) + result = runner.build_and_run(fabric_agent_with_knowledge, inputs) - # Verify the crew accessed the knowledge + # Verify the fabric agent accessed the knowledge. assert result is not None assert isinstance(result, str) assert len(result) > 0 @@ -157,7 +157,7 @@ def test_tool_usage( check_api_key: None, simple_agent_config: dict[str, Any], ) -> None: - """Test crew with tools assigned to agents. + """Test fabric_agent with tools assigned to agents. Args: check_api_key: Fixture to check for required API keys. @@ -195,18 +195,18 @@ def get_test_data(query: str) -> str: task = runner.build_task(task_config, agent) assert task is not None - # Create a minimal crew + # Create a minimal fabric_agent from crewai import Crew, Process - crew = Crew( + fabric_agent = Crew( agents=[agent], tasks=[task], process=Process.sequential, verbose=True, ) - # Run the crew - result = crew.kickoff() + # Run the fabric_agent + result = fabric_agent.kickoff() # Verify the tool was used assert result is not None diff --git a/packages/agentic-fabric/tests/e2e/test_langgraph_e2e.py b/packages/agentic-fabric/tests/e2e/test_langgraph_e2e.py index b958997..0fb0ee8 100644 --- a/packages/agentic-fabric/tests/e2e/test_langgraph_e2e.py +++ b/packages/agentic-fabric/tests/e2e/test_langgraph_e2e.py @@ -36,20 +36,20 @@ class TestLangGraphReActAgent: def test_react_agent_execution( self, check_api_key: None, - simple_crew_config: dict[str, Any], + simple_fabric_agent_config: dict[str, Any], ) -> None: """Test simple ReAct agent execution. Args: check_api_key: Fixture to check for required API keys. - simple_crew_config: Simple crew configuration fixture. + simple_fabric_agent_config: Simple fabric_agent configuration fixture. """ from agentic_fabric.runners.langgraph_runner import LangGraphRunner runner = LangGraphRunner() - # Build the agent/crew - agent = runner.build_crew(simple_crew_config) + # Build the agent/fabric_agent + agent = runner.build_fabric_agent(simple_fabric_agent_config) assert agent is not None # Run with simple input @@ -66,13 +66,13 @@ def test_react_agent_execution( def test_build_and_run( self, check_api_key: None, - simple_crew_config: dict[str, Any], + simple_fabric_agent_config: dict[str, Any], ) -> None: """Test the convenience build_and_run method. Args: check_api_key: Fixture to check for required API keys. - simple_crew_config: Simple crew configuration fixture. + simple_fabric_agent_config: Simple fabric_agent configuration fixture. """ from agentic_fabric.runners.langgraph_runner import LangGraphRunner @@ -80,7 +80,7 @@ def test_build_and_run( # Use build_and_run convenience method inputs = {"input": "What is the largest ocean on Earth?"} - result = runner.build_and_run(simple_crew_config, inputs) + result = runner.build_and_run(simple_fabric_agent_config, inputs) # Verify we got a response about Pacific Ocean assert result is not None @@ -145,7 +145,7 @@ class TestLangGraphMultiStepPrompt: def test_agent_handles_multi_step_prompt( self, check_api_key: None, - multi_agent_crew_config: dict[str, Any], + multi_agent_fabric_agent_config: dict[str, Any], ) -> None: """Test that the LangGraph agent can handle a multi-step prompt. @@ -154,19 +154,19 @@ def test_agent_handles_multi_step_prompt( Args: check_api_key: Fixture to check for required API keys. - multi_agent_crew_config: Multi-agent crew configuration fixture. + multi_agent_fabric_agent_config: Multi-agent fabric_agent configuration fixture. """ from agentic_fabric.runners.langgraph_runner import LangGraphRunner runner = LangGraphRunner() - # Build a crew from config - crew = runner.build_crew(multi_agent_crew_config) - assert crew is not None + # Build a fabric_agent from config + fabric_agent = runner.build_fabric_agent(multi_agent_fabric_agent_config) + assert fabric_agent is not None # Execute with input that requires multi-step reasoning with distinct answers inputs = {"input": "First, tell me what 5 + 3 equals, then tell me what 10 - 3 equals."} - result = runner.run(crew, inputs) + result = runner.run(fabric_agent, inputs) # Verify the runner properly managed state through the workflow assert result is not None diff --git a/packages/agentic-fabric/tests/e2e/test_strands_e2e.py b/packages/agentic-fabric/tests/e2e/test_strands_e2e.py index 922df55..13af4ac 100644 --- a/packages/agentic-fabric/tests/e2e/test_strands_e2e.py +++ b/packages/agentic-fabric/tests/e2e/test_strands_e2e.py @@ -40,20 +40,20 @@ class TestStrandsAgentExecution: def test_strands_agent_execution( self, check_aws_credentials: None, - simple_crew_config: dict[str, Any], + simple_fabric_agent_config: dict[str, Any], ) -> None: """Test simple Strands agent execution. Args: check_aws_credentials: Fixture to check for required AWS credentials. - simple_crew_config: Simple crew configuration fixture. + simple_fabric_agent_config: Simple fabric_agent configuration fixture. """ from agentic_fabric.runners.strands_runner import StrandsRunner runner = StrandsRunner() - # Build the agent from crew config - agent = runner.build_crew(simple_crew_config) + # Build the agent from fabric_agent config + agent = runner.build_fabric_agent(simple_fabric_agent_config) assert agent is not None # Run with simple input @@ -70,13 +70,13 @@ def test_strands_agent_execution( def test_build_and_run( self, check_aws_credentials: None, - simple_crew_config: dict[str, Any], + simple_fabric_agent_config: dict[str, Any], ) -> None: """Test the convenience build_and_run method. Args: check_aws_credentials: Fixture to check for required AWS credentials. - simple_crew_config: Simple crew configuration fixture. + simple_fabric_agent_config: Simple fabric_agent configuration fixture. """ from agentic_fabric.runners.strands_runner import StrandsRunner @@ -84,7 +84,7 @@ def test_build_and_run( # Use build_and_run convenience method inputs = {"input": "What is the smallest prime number?"} - result = runner.build_and_run(simple_crew_config, inputs) + result = runner.build_and_run(simple_fabric_agent_config, inputs) # Verify we got a response assert result is not None @@ -157,20 +157,20 @@ class TestStrandsSystemPrompt: def test_system_prompt_generation( self, check_aws_credentials: None, - multi_agent_crew_config: dict[str, Any], + multi_agent_fabric_agent_config: dict[str, Any], ) -> None: - """Test that system prompts are generated correctly from crew config. + """Test that system prompts are generated correctly from fabric_agent config. Args: check_aws_credentials: Fixture to check for required AWS credentials. - multi_agent_crew_config: Multi-agent crew configuration fixture. + multi_agent_fabric_agent_config: Multi-agent fabric_agent configuration fixture. """ from agentic_fabric.runners.strands_runner import StrandsRunner runner = StrandsRunner() - # Build system prompt (internal method, testing through crew build) - agent = runner.build_crew(multi_agent_crew_config) + # Build system prompt (internal method, testing through fabric_agent build) + agent = runner.build_fabric_agent(multi_agent_fabric_agent_config) assert agent is not None # Run the agent to verify the system prompt is working @@ -190,7 +190,7 @@ class TestStrandsBedrockIntegration: def test_bedrock_integration( self, check_aws_credentials: None, - simple_crew_config: dict[str, Any], + simple_fabric_agent_config: dict[str, Any], ) -> None: """Test Strands with AWS Bedrock model provider. @@ -198,13 +198,13 @@ def test_bedrock_integration( Args: check_aws_credentials: Fixture to check for required AWS credentials. - simple_crew_config: Simple crew configuration fixture. + simple_fabric_agent_config: Simple fabric_agent configuration fixture. """ from agentic_fabric.runners.strands_runner import StrandsRunner # Add Bedrock model configuration bedrock_config = { - **simple_crew_config, + **simple_fabric_agent_config, "llm": { "provider": "bedrock", "model": "anthropic.claude-haiku-4-5-20251001-v1:0", diff --git a/packages/agentic-fabric/tests/test_agentic_data.py b/packages/agentic-fabric/tests/test_agentic_data.py index c650e9c..f5f97b5 100644 --- a/packages/agentic-fabric/tests/test_agentic_data.py +++ b/packages/agentic-fabric/tests/test_agentic_data.py @@ -50,7 +50,7 @@ def test_select_runtime_rejects_active_manifest_conflict(monkeypatch: pytest.Mon data = AgenticData(active_runtime="langgraph") with pytest.raises(ValueError, match="conflicts"): - data.select_runtime(crew_config={"required_framework": "crewai"}) + data.select_runtime(fabric_agent_config={"required_framework": "crewai"}) def test_select_runtime_uses_active_runtime(monkeypatch: pytest.MonkeyPatch) -> None: @@ -61,18 +61,18 @@ def test_select_runtime_uses_active_runtime(monkeypatch: pytest.MonkeyPatch) -> assert data.select_runtime() == "strands" -def test_run_agent_uses_registered_crew_and_runtime(monkeypatch: pytest.MonkeyPatch) -> None: - """Named agents should route through run_crew_auto with merged inputs.""" +def test_run_fabric_agent_uses_registered_config_and_runtime(monkeypatch: pytest.MonkeyPatch) -> None: + """Named fabric agents should route through run_fabric_agent_auto with merged inputs.""" calls: list[tuple[dict[str, Any], dict[str, Any], str | None]] = [] - def fake_run_crew_auto(crew_config: dict[str, Any], inputs: dict[str, Any], framework: str | None = None) -> str: - calls.append((crew_config, inputs, framework)) + def fake_run_fabric_agent_auto(fabric_agent_config: dict[str, Any], inputs: dict[str, Any], framework: str | None = None) -> str: + calls.append((fabric_agent_config, inputs, framework)) return "done" monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: runtime == "crewai") - monkeypatch.setattr("agentic_fabric.core.decomposer.run_crew_auto", fake_run_crew_auto) + monkeypatch.setattr("agentic_fabric.core.decomposer.run_fabric_agent_auto", fake_run_fabric_agent_auto) - data = AgenticData(agent_registry={"reviewer": {"name": "review", "agents": {}, "tasks": {}}}) + data = AgenticData(fabric_agents={"reviewer": {"name": "review", "agents": {}, "tasks": {}}}) result = data.run_reviewer({"code": "x"}, severity="high") @@ -80,12 +80,12 @@ def fake_run_crew_auto(crew_config: dict[str, Any], inputs: dict[str, Any], fram assert calls == [({"name": "review", "agents": {}, "tasks": {}}, {"code": "x", "severity": "high"}, "crewai")] -def test_unknown_agent_lists_registered_names() -> None: - """Unknown agent errors should include the registered names.""" - data = AgenticData(agent_registry={"reviewer": {"name": "review"}}) +def test_unknown_fabric_agent_lists_registered_names() -> None: + """Unknown fabric agent errors should include the registered names.""" + data = AgenticData(fabric_agents={"reviewer": {"name": "review"}}) with pytest.raises(KeyError, match="reviewer"): - data.run_agent("writer") + data.run_fabric_agent("writer") def test_fallback_vendor_guidance_when_vendor_fabric_missing() -> None: @@ -147,16 +147,16 @@ def cast(self, value: Any) -> VendorData: assert data.value == "value" -def test_agent_registry_is_read_only_and_unregister_is_chainable() -> None: - """Agent registry snapshots should be immutable from the public property.""" - data = AgenticData(agent_registry={"reviewer": {"name": "review"}}) +def test_fabric_agents_are_read_only_and_unregister_is_chainable() -> None: + """Fabric agent registry snapshots should be immutable from the public property.""" + data = AgenticData(fabric_agents={"reviewer": {"name": "review"}}) with pytest.raises(TypeError): - data.agent_registry["writer"] = {"name": "write"} # type: ignore[index] + data.fabric_agents["writer"] = {"name": "write"} # type: ignore[index] - assert data.unregister_agent("reviewer") is data - assert data.agent_registry == {} - assert data.unregister_agent("missing") is data + assert data.unregister_fabric_agent("reviewer") is data + assert data.fabric_agents == {} + assert data.unregister_fabric_agent("missing") is data def test_use_runtime_auto_and_clear_runtime(monkeypatch: pytest.MonkeyPatch) -> None: @@ -188,8 +188,8 @@ def test_select_runtime_rejects_explicit_manifest_conflict(monkeypatch: pytest.M monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: True) data = AgenticData() - with pytest.raises(ValueError, match="Crew requires crewai but langgraph was requested"): - data.select_runtime("langgraph", crew_config={"runtime": "crewai"}) + with pytest.raises(ValueError, match="Fabric agent requires crewai but langgraph was requested"): + data.select_runtime("langgraph", fabric_agent_config={"runtime": "crewai"}) def test_select_runtime_uses_required_runtime_and_auto_detection(monkeypatch: pytest.MonkeyPatch) -> None: @@ -198,7 +198,7 @@ def test_select_runtime_uses_required_runtime_and_auto_detection(monkeypatch: py monkeypatch.setattr("agentic_fabric.core.decomposer.detect_framework", lambda: "strands") data = AgenticData() - assert data.select_runtime(crew_config={"framework": "langgraph"}) == "langgraph" + assert data.select_runtime(fabric_agent_config={"framework": "langgraph"}) == "langgraph" assert data.select_runtime() == "strands" @@ -210,18 +210,18 @@ def test_select_runtime_reports_unavailable_requested_runtime(monkeypatch: pytes AgenticData().select_runtime("crewai") -def test_run_agent_accepts_direct_mapping(monkeypatch: pytest.MonkeyPatch) -> None: - """Direct crew config mappings should bypass registry lookup.""" +def test_run_fabric_agent_accepts_direct_mapping(monkeypatch: pytest.MonkeyPatch) -> None: + """Direct fabric agent config mappings should bypass registry lookup.""" calls: list[tuple[dict[str, Any], dict[str, Any], str | None]] = [] - def fake_run_crew_auto(crew_config: dict[str, Any], inputs: dict[str, Any], framework: str | None = None) -> str: - calls.append((crew_config, inputs, framework)) + def fake_run_fabric_agent_auto(fabric_agent_config: dict[str, Any], inputs: dict[str, Any], framework: str | None = None) -> str: + calls.append((fabric_agent_config, inputs, framework)) return "done" monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: runtime == "crewai") - monkeypatch.setattr("agentic_fabric.core.decomposer.run_crew_auto", fake_run_crew_auto) + monkeypatch.setattr("agentic_fabric.core.decomposer.run_fabric_agent_auto", fake_run_fabric_agent_auto) - result = AgenticData().run_agent({"name": "direct", "runtime": "crewai"}, {"a": 1}, b=2) + result = AgenticData().run_fabric_agent({"name": "direct", "runtime": "crewai"}, {"a": 1}, b=2) assert result == "done" assert calls == [({"name": "direct", "runtime": "crewai"}, {"a": 1, "b": 2}, "crewai")] @@ -262,11 +262,11 @@ def fake_vendor_capability_tools( def test_dynamic_helpers_and_missing_attributes(monkeypatch: pytest.MonkeyPatch) -> None: - """Registered agents should appear as dynamic run_ helpers.""" + """Registered fabric agents should appear as dynamic run_ helpers.""" monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda runtime: True) - monkeypatch.setattr("agentic_fabric.core.decomposer.run_crew_auto", lambda crew_config, inputs, framework: "done") + monkeypatch.setattr("agentic_fabric.core.decomposer.run_fabric_agent_auto", lambda fabric_agent_config, inputs, framework: "done") - data = AgenticData(agent_registry={"reviewer": {"name": "reviewer", "runtime": "crewai"}}) + data = AgenticData(fabric_agents={"reviewer": {"name": "reviewer", "runtime": "crewai"}}) assert "run_reviewer" in dir(data) assert data.run_reviewer() == "done" diff --git a/packages/agentic-fabric/tests/test_capabilities.py b/packages/agentic-fabric/tests/test_capabilities.py index cae96f7..893b8ce 100644 --- a/packages/agentic-fabric/tests/test_capabilities.py +++ b/packages/agentic-fabric/tests/test_capabilities.py @@ -22,11 +22,11 @@ def test_base_runner_declares_runtime_capabilities(self) -> None: """Runner methods should expose read-only capability metadata.""" class TestRunner(BaseRunner): - def build_crew(self, crew_config: dict[str, Any]) -> Any: - return crew_config + def build_fabric_agent(self, fabric_agent_config: dict[str, Any]) -> Any: + return fabric_agent_config - def run(self, crew: Any, inputs: dict[str, Any]) -> str: - return f"{crew}:{inputs}" + def run(self, fabric_agent: Any, inputs: dict[str, Any]) -> str: + return f"{fabric_agent}:{inputs}" def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: return agent_config @@ -36,9 +36,9 @@ def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: runner = TestRunner() - assert "build_crew" in runner.capability_map() - assert "run_crew" in runner.capability_map() - assert runner.call_capability("run_crew", {"name": "crew"}, {"task": "go"}) == "{'name': 'crew'}:{'task': 'go'}" + assert "build_fabric_agent" in runner.capability_map() + assert "run_fabric_agent" in runner.capability_map() + assert runner.call_capability("run_fabric_agent", {"name": "fabric_agent"}, {"task": "go"}) == "{'name': 'fabric_agent'}:{'task': 'go'}" with pytest.raises(TypeError): runner.capability_map()["new"] = runner.capability_map()["run"] # type: ignore[index] diff --git a/packages/agentic-fabric/tests/test_cli_commands.py b/packages/agentic-fabric/tests/test_cli_commands.py index 1f8a05b..093e71b 100644 --- a/packages/agentic-fabric/tests/test_cli_commands.py +++ b/packages/agentic-fabric/tests/test_cli_commands.py @@ -39,18 +39,18 @@ def run(self, **kwargs: Any) -> str: return self.result -def test_cmd_list_text_displays_framework_marked_crews(capsys: pytest.CaptureFixture[str]) -> None: +def test_cmd_list_text_displays_framework_marked_fabric_agents(capsys: pytest.CaptureFixture[str]) -> None: args = SimpleNamespace(package=None, framework=None, json=False) with patch.object( cli, - "list_crews", + "list_fabric_agents", return_value={"pkg": [{"name": "reviewer", "description": "reviews code", "required_framework": "crewai"}]}, ): cli.cmd_list(args) output = capsys.readouterr().out - assert "AVAILABLE CREWS" in output + assert "AVAILABLE FABRIC AGENTS" in output assert "pkg" in output assert "reviewer [crewai]: reviews code" in output @@ -60,13 +60,13 @@ def test_cmd_list_json_flattens_packages(capsys: pytest.CaptureFixture[str]) -> with patch.object( cli, - "list_crews", + "list_fabric_agents", return_value={"pkg": [{"name": "builder", "description": "builds", "required_framework": "langgraph"}]}, ): cli.cmd_list(args) assert json.loads(capsys.readouterr().out) == { - "crews": [ + "fabric_agents": [ { "package": "pkg", "name": "builder", @@ -85,7 +85,7 @@ def test_cmd_run_json_success_auto_detects_framework( json=True, runner=None, package="pkg", - crew="reviewer", + fabric_agent="reviewer", file=None, input="review this", framework="auto", @@ -93,9 +93,9 @@ def test_cmd_run_json_success_auto_detects_framework( with ( patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), - patch.object(cli, "get_crew_config", return_value={"name": "reviewer"}), + patch.object(cli, "get_fabric_agent_config", return_value={"name": "reviewer"}), patch("agentic_fabric.core.decomposer.detect_framework", return_value="crewai"), - patch("agentic_fabric.core.decomposer.run_crew_auto", return_value="done") as run_crew_auto, + patch("agentic_fabric.core.decomposer.run_fabric_agent_auto", return_value="done") as run_fabric_agent_auto, ): cli.cmd_run(args) @@ -103,7 +103,7 @@ def test_cmd_run_json_success_auto_detects_framework( assert data["success"] is True assert data["output"] == "done" assert data["framework_used"] == "crewai" - run_crew_auto.assert_called_once_with( + run_fabric_agent_auto.assert_called_once_with( {"name": "reviewer"}, inputs={"spec": "review this", "component_spec": "review this", "input": "review this"}, framework="crewai", @@ -120,7 +120,7 @@ def test_cmd_run_json_uses_file_input_and_requested_framework( json=True, runner=None, package="pkg", - crew="reviewer", + fabric_agent="reviewer", file=str(input_file), input=None, framework="strands", @@ -128,20 +128,20 @@ def test_cmd_run_json_uses_file_input_and_requested_framework( with ( patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".strands"}), - patch.object(cli, "get_crew_config", return_value={"name": "reviewer"}), - patch("agentic_fabric.core.decomposer.run_crew_auto", return_value="done") as run_crew_auto, + patch.object(cli, "get_fabric_agent_config", return_value={"name": "reviewer"}), + patch("agentic_fabric.core.decomposer.run_fabric_agent_auto", return_value="done") as run_fabric_agent_auto, ): cli.cmd_run(args) data = json.loads(capsys.readouterr().out) assert data["success"] is True assert data["framework_used"] == "strands" - assert run_crew_auto.call_args.kwargs["inputs"]["input"] == "from file" - assert run_crew_auto.call_args.kwargs["framework"] == "strands" + assert run_fabric_agent_auto.call_args.kwargs["inputs"]["input"] == "from file" + assert run_fabric_agent_auto.call_args.kwargs["framework"] == "strands" def test_cmd_run_dispatches_single_agent_runner(capsys: pytest.CaptureFixture[str]) -> None: - args = SimpleNamespace(json=False, runner="fake", package=None, crew=None) + args = SimpleNamespace(json=False, runner="fake", package=None, fabric_agent=None) calls: list[tuple[SimpleNamespace, bool]] = [] with patch.object(cli, "_cmd_run_single_agent", side_effect=lambda args, use_json, start_time: calls.append((args, use_json))): @@ -156,7 +156,7 @@ def test_cmd_run_json_reports_missing_package(capsys: pytest.CaptureFixture[str] json=True, runner=None, package="missing", - crew="reviewer", + fabric_agent="reviewer", file=None, input="task", framework="auto", @@ -182,7 +182,7 @@ def test_cmd_run_json_reports_execution_errors( json=True, runner=None, package="pkg", - crew="reviewer", + fabric_agent="reviewer", file=None, input="task", framework="auto", @@ -190,13 +190,13 @@ def test_cmd_run_json_reports_execution_errors( with ( patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), - patch.object(cli, "get_crew_config", side_effect=ValueError("bad crew")), + patch.object(cli, "get_fabric_agent_config", side_effect=ValueError("bad fabric agent")), pytest.raises(SystemExit) as exc_info, ): cli.cmd_run(args) assert exc_info.value.code == 1 - assert json.loads(capsys.readouterr().out)["error"] == "bad crew" + assert json.loads(capsys.readouterr().out)["error"] == "bad fabric agent" @pytest.mark.parametrize("use_json", [True, False]) @@ -206,7 +206,7 @@ def test_cmd_run_reports_file_read_errors(capsys: pytest.CaptureFixture[str], us json=use_json, runner=None, package="pkg", - crew="reviewer", + fabric_agent="reviewer", file="/definitely/missing/task.txt", input=None, framework="auto", @@ -224,23 +224,23 @@ def test_cmd_run_reports_file_read_errors(capsys: pytest.CaptureFixture[str], us def test_cmd_run_text_reports_missing_arguments(capsys: pytest.CaptureFixture[str]) -> None: - args = SimpleNamespace(json=False, runner=None, package=None, crew=None) + args = SimpleNamespace(json=False, runner=None, package=None, fabric_agent=None) with pytest.raises(SystemExit) as exc_info: cli.cmd_run(args) assert exc_info.value.code == 2 - assert "Package and crew are required" in capsys.readouterr().out + assert "Package and fabric_agent are required" in capsys.readouterr().out def test_cmd_run_json_reports_missing_arguments(capsys: pytest.CaptureFixture[str]) -> None: - args = SimpleNamespace(json=True, runner=None, package=None, crew=None) + args = SimpleNamespace(json=True, runner=None, package=None, fabric_agent=None) with pytest.raises(SystemExit) as exc_info: cli.cmd_run(args) assert exc_info.value.code == 2 - assert json.loads(capsys.readouterr().out)["error"].startswith("Package and crew are required") + assert json.loads(capsys.readouterr().out)["error"].startswith("Package and fabric_agent are required") def test_cmd_run_text_reports_missing_package(capsys: pytest.CaptureFixture[str]) -> None: @@ -248,7 +248,7 @@ def test_cmd_run_text_reports_missing_package(capsys: pytest.CaptureFixture[str] json=False, runner=None, package="missing", - crew="reviewer", + fabric_agent="reviewer", file=None, input="task", framework="auto", @@ -274,21 +274,21 @@ def test_cmd_run_text_uses_empty_input_and_requested_or_auto_framework( json=False, runner=None, package="pkg", - crew="reviewer", + fabric_agent="reviewer", file=None, input=None, framework="langgraph", ) calls: list[dict[str, Any]] = [] - def fake_run_crew_auto(crew_config: dict[str, Any], inputs: dict[str, str], framework: str | None = None) -> str: - calls.append({"crew_config": crew_config, "inputs": inputs, "framework": framework}) + def fake_run_fabric_agent_auto(fabric_agent_config: dict[str, Any], inputs: dict[str, str], framework: str | None = None) -> str: + calls.append({"fabric_agent_config": fabric_agent_config, "inputs": inputs, "framework": framework}) return "done" with ( - patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crew"}), - patch.object(cli, "get_crew_config", return_value={"name": "reviewer"}), - patch("agentic_fabric.core.decomposer.run_crew_auto", side_effect=fake_run_crew_auto), + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".fabric"}), + patch.object(cli, "get_fabric_agent_config", return_value={"name": "reviewer"}), + patch("agentic_fabric.core.decomposer.run_fabric_agent_auto", side_effect=fake_run_fabric_agent_auto), ): cli.cmd_run(args) @@ -299,10 +299,10 @@ def fake_run_crew_auto(crew_config: dict[str, Any], inputs: dict[str, str], fram args.framework = "auto" with ( - patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crew"}), - patch.object(cli, "get_crew_config", return_value={"name": "reviewer"}), + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".fabric"}), + patch.object(cli, "get_fabric_agent_config", return_value={"name": "reviewer"}), patch("agentic_fabric.core.decomposer.detect_framework", return_value="strands"), - patch("agentic_fabric.core.decomposer.run_crew_auto", return_value="done"), + patch("agentic_fabric.core.decomposer.run_fabric_agent_auto", return_value="done"), ): cli.cmd_run(args) @@ -314,7 +314,7 @@ def test_cmd_run_text_success_and_error_paths(tmp_path: Path, capsys: pytest.Cap json=False, runner=None, package="pkg", - crew="reviewer", + fabric_agent="reviewer", file=None, input="task", framework="auto", @@ -322,8 +322,8 @@ def test_cmd_run_text_success_and_error_paths(tmp_path: Path, capsys: pytest.Cap with ( patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), - patch.object(cli, "get_crew_config", return_value={"name": "reviewer", "required_framework": "crewai"}), - patch("agentic_fabric.core.decomposer.run_crew_auto", return_value="done"), + patch.object(cli, "get_fabric_agent_config", return_value={"name": "reviewer", "required_framework": "crewai"}), + patch("agentic_fabric.core.decomposer.run_fabric_agent_auto", return_value="done"), ): cli.cmd_run(args) @@ -334,7 +334,7 @@ def test_cmd_run_text_success_and_error_paths(tmp_path: Path, capsys: pytest.Cap with ( patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), - patch.object(cli, "get_crew_config", side_effect=RuntimeError("boom")), + patch.object(cli, "get_fabric_agent_config", side_effect=RuntimeError("boom")), pytest.raises(SystemExit) as exc_info, ): cli.cmd_run(args) @@ -344,7 +344,7 @@ def test_cmd_run_text_success_and_error_paths(tmp_path: Path, capsys: pytest.Cap def test_cmd_info_json_success(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: - args = SimpleNamespace(package="pkg", crew="reviewer", json=True) + args = SimpleNamespace(package="pkg", fabric_agent="reviewer", json=True) config = { "description": "Reviews code", "required_framework": "crewai", @@ -355,7 +355,7 @@ def test_cmd_info_json_success(tmp_path: Path, capsys: pytest.CaptureFixture[str with ( patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), - patch.object(cli, "get_crew_config", return_value=config), + patch.object(cli, "get_fabric_agent_config", return_value=config), ): cli.cmd_info(args) @@ -367,21 +367,21 @@ def test_cmd_info_json_success(tmp_path: Path, capsys: pytest.CaptureFixture[str def test_cmd_info_json_reports_config_error(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: - args = SimpleNamespace(package="pkg", crew="missing", json=True) + args = SimpleNamespace(package="pkg", fabric_agent="missing", json=True) with ( patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), - patch.object(cli, "get_crew_config", side_effect=ValueError("missing crew")), + patch.object(cli, "get_fabric_agent_config", side_effect=ValueError("missing fabric agent")), pytest.raises(SystemExit) as exc_info, ): cli.cmd_info(args) assert exc_info.value.code == 2 - assert json.loads(capsys.readouterr().out) == {"error": "missing crew"} + assert json.loads(capsys.readouterr().out) == {"error": "missing fabric agent"} def test_cmd_info_json_reports_missing_package(capsys: pytest.CaptureFixture[str]) -> None: - args = SimpleNamespace(package="missing", crew="reviewer", json=True) + args = SimpleNamespace(package="missing", fabric_agent="reviewer", json=True) with ( patch.object(cli, "discover_packages", return_value={"known": Path(".crewai")}), @@ -396,7 +396,7 @@ def test_cmd_info_json_reports_missing_package(capsys: pytest.CaptureFixture[str def test_cmd_info_text_success_and_errors(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: - args = SimpleNamespace(package="pkg", crew="reviewer", json=False) + args = SimpleNamespace(package="pkg", fabric_agent="reviewer", json=False) config = { "description": "Reviews code", "agents": {"reviewer": {"role": "Code Reviewer"}}, @@ -406,12 +406,12 @@ def test_cmd_info_text_success_and_errors(tmp_path: Path, capsys: pytest.Capture with ( patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), - patch.object(cli, "get_crew_config", return_value=config), + patch.object(cli, "get_fabric_agent_config", return_value=config), ): cli.cmd_info(args) output = capsys.readouterr().out - assert "CREW: pkg/reviewer" in output + assert "FABRIC AGENT: pkg/reviewer" in output assert "Code Reviewer" in output assert "Review the change" in output @@ -426,13 +426,13 @@ def test_cmd_info_text_success_and_errors(tmp_path: Path, capsys: pytest.Capture with ( patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), - patch.object(cli, "get_crew_config", side_effect=ValueError("missing crew")), + patch.object(cli, "get_fabric_agent_config", side_effect=ValueError("missing fabric agent")), pytest.raises(SystemExit) as exc_info, ): cli.cmd_info(args) assert exc_info.value.code == 2 - assert "missing crew" in capsys.readouterr().out + assert "missing fabric agent" in capsys.readouterr().out def test_cmd_list_runners_json_skips_bad_profiles(capsys: pytest.CaptureFixture[str]) -> None: @@ -603,7 +603,7 @@ def test_single_agent_text_success_unavailable_and_error_paths( ) -> None: input_file = tmp_path / "task.txt" input_file.write_text("from file", encoding="utf-8") - package_dir = tmp_path / "pkg" / ".crew" + package_dir = tmp_path / "pkg" / ".fabric" fake_runner = FakeCliRunner(result="fixed") args = SimpleNamespace( json=False, diff --git a/packages/agentic-fabric/tests/test_cli_smoke.py b/packages/agentic-fabric/tests/test_cli_smoke.py index baccc46..57147a6 100644 --- a/packages/agentic-fabric/tests/test_cli_smoke.py +++ b/packages/agentic-fabric/tests/test_cli_smoke.py @@ -37,14 +37,14 @@ def test_list_command_with_no_packages(self, capsys) -> None: return_value={}, ), patch( - "agentic_fabric.main.list_crews", + "agentic_fabric.main.list_fabric_agents", return_value={}, ), ): main() captured = capsys.readouterr() - assert "No packages" in captured.out or "crews" in captured.out.lower() + assert "No packages" in captured.out or "fabric_agents" in captured.out.lower() def test_list_command_json_output(self, capsys) -> None: """'list --json' should produce valid JSON.""" @@ -53,7 +53,7 @@ def test_list_command_json_output(self, capsys) -> None: with ( patch("sys.argv", ["agentic-fabric", "list", "--json"]), patch( - "agentic_fabric.main.list_crews", + "agentic_fabric.main.list_fabric_agents", return_value={}, ), ): @@ -61,11 +61,11 @@ def test_list_command_json_output(self, capsys) -> None: captured = capsys.readouterr() data = json.loads(captured.out) - assert "crews" in data - assert isinstance(data["crews"], list) + assert "fabric_agents" in data + assert isinstance(data["fabric_agents"], list) - def test_run_missing_package_and_crew_exits_2(self) -> None: - """'run' without package or crew should exit with code 2.""" + def test_run_missing_package_and_fabric_agent_exits_2(self) -> None: + """'run' without package or fabric_agent should exit with code 2.""" with ( patch("sys.argv", ["agentic-fabric", "run"]), pytest.raises(SystemExit) as exc_info, @@ -76,7 +76,7 @@ def test_run_missing_package_and_crew_exits_2(self) -> None: def test_run_nonexistent_package_exits_2(self) -> None: """'run' with a non-existent package should exit with code 2.""" with ( - patch("sys.argv", ["agentic-fabric", "run", "nonexistent", "some_crew", "--input", "test"]), + patch("sys.argv", ["agentic-fabric", "run", "nonexistent", "some_fabric_agent", "--input", "test"]), patch("agentic_fabric.main.discover_packages", return_value={}), pytest.raises(SystemExit) as exc_info, ): @@ -86,21 +86,21 @@ def test_run_nonexistent_package_exits_2(self) -> None: def test_info_nonexistent_package_exits_2(self) -> None: """'info' with non-existent package should exit with code 2.""" with ( - patch("sys.argv", ["agentic-fabric", "info", "nonexistent", "some_crew"]), + patch("sys.argv", ["agentic-fabric", "info", "nonexistent", "some_fabric_agent"]), patch("agentic_fabric.main.discover_packages", return_value={}), pytest.raises(SystemExit) as exc_info, ): main() assert exc_info.value.code == 2 - def test_info_nonexistent_crew_exits_2(self, tmp_path) -> None: - """'info' with non-existent crew should exit with code 2.""" + def test_info_missing_fabric_agent_exits_2(self, tmp_path) -> None: + """'info' with non-existent fabric agent should exit with code 2.""" config_dir = tmp_path / ".crewai" config_dir.mkdir() - (config_dir / "manifest.yaml").write_text("crews: {}\n") + (config_dir / "manifest.yaml").write_text("fabric_agents: {}\n") with ( - patch("sys.argv", ["agentic-fabric", "info", "pkg", "missing_crew"]), + patch("sys.argv", ["agentic-fabric", "info", "pkg", "missing_fabric_agent"]), patch( "agentic_fabric.main.discover_packages", return_value={"pkg": config_dir}, diff --git a/packages/agentic-fabric/tests/test_connector_builder_crew.py b/packages/agentic-fabric/tests/test_connector_builder_fabric.py similarity index 74% rename from packages/agentic-fabric/tests/test_connector_builder_crew.py rename to packages/agentic-fabric/tests/test_connector_builder_fabric.py index 17a7e03..dc504a6 100644 --- a/packages/agentic-fabric/tests/test_connector_builder_crew.py +++ b/packages/agentic-fabric/tests/test_connector_builder_fabric.py @@ -1,4 +1,4 @@ -"""Tests for the Connector Builder Crew.""" +"""Tests for the connector-builder fabric agent.""" from __future__ import annotations @@ -58,8 +58,8 @@ def import_connector_builder_with_fake_crewai(monkeypatch: pytest.MonkeyPatch) - fake_crewai.Task = FakeTask fake_crewai.Crew = FakeCrew monkeypatch.setitem(sys.modules, "crewai", fake_crewai) - sys.modules.pop("agentic_fabric.crews.connector_builder.connector_builder_crew", None) - return importlib.import_module("agentic_fabric.crews.connector_builder.connector_builder_crew") + sys.modules.pop("agentic_fabric.fabric_agents.connector_builder.connector_builder_fabric", None) + return importlib.import_module("agentic_fabric.fabric_agents.connector_builder.connector_builder_fabric") @pytest.fixture @@ -67,40 +67,40 @@ def connector_builder_module(monkeypatch: pytest.MonkeyPatch): """Yield a connector-builder module backed by fake CrewAI classes.""" module = import_connector_builder_with_fake_crewai(monkeypatch) yield module - sys.modules.pop("agentic_fabric.crews.connector_builder.connector_builder_crew", None) + sys.modules.pop("agentic_fabric.fabric_agents.connector_builder.connector_builder_fabric", None) -def test_connector_builder_crew_initializes_and_kicks_off( +def test_connector_builder_fabric_initializes_and_kicks_off( monkeypatch: pytest.MonkeyPatch, connector_builder_module: Any, ) -> None: - """ConnectorBuilderCrew should build configured agents/tasks and delegate kickoff.""" + """ConnectorBuilderFabricAgent should build configured agents/tasks and delegate kickoff.""" module = connector_builder_module monkeypatch.setattr(module, "resolve_tools", lambda tools: [f"resolved:{tool}" for tool in tools]) - crew_instance = module.ConnectorBuilderCrew(output_dir="test_output") + fabric_agent_instance = module.ConnectorBuilderFabricAgent(output_dir="test_output") assert len(FakeAgent.calls) == 3 assert len(FakeTask.calls) == 3 - assert isinstance(crew_instance.crew, FakeCrew) + assert isinstance(fabric_agent_instance.fabric_agent, FakeCrew) assert FakeAgent.calls[0]["tools"] == ["resolved:ScrapeWebsiteTool", "resolved:CrawlWebsiteTool"] assert FakeAgent.calls[2]["tools"] == ["resolved:FileWriteTool"] assert "test_output directory" in FakeTask.calls[2]["description"] assert FakeCrew.calls[0]["agents"] == [ - crew_instance.doc_scraper, - crew_instance.api_analyzer, - crew_instance.code_generator, + fabric_agent_instance.doc_scraper, + fabric_agent_instance.api_analyzer, + fabric_agent_instance.code_generator, ] assert FakeCrew.calls[0]["tasks"] == [ - crew_instance.scrape_docs, - crew_instance.analyze_api, - crew_instance.generate_code, + fabric_agent_instance.scrape_docs, + fabric_agent_instance.analyze_api, + fabric_agent_instance.generate_code, ] - result = crew_instance.kickoff(inputs={"url": "http://example.com"}) + result = fabric_agent_instance.kickoff(inputs={"url": "http://example.com"}) assert result == "Success" - assert crew_instance.crew.kickoff_inputs == {"url": "http://example.com"} + assert fabric_agent_instance.fabric_agent.kickoff_inputs == {"url": "http://example.com"} def test_connector_builder_kickoff_returns_raw_result( @@ -118,9 +118,9 @@ def kickoff(self, inputs: dict[str, Any]) -> Any: monkeypatch.setattr(module, "Crew", RawCrew) monkeypatch.setattr(module, "resolve_tools", lambda tools: []) - crew_instance = module.ConnectorBuilderCrew() + fabric_agent_instance = module.ConnectorBuilderFabricAgent() - assert crew_instance.kickoff(inputs={"url": "http://example.com"}) == "raw success" + assert fabric_agent_instance.kickoff(inputs={"url": "http://example.com"}) == "raw success" def test_connector_builder_reports_missing_crewai(monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/packages/agentic-fabric/tests/test_core_runner.py b/packages/agentic-fabric/tests/test_core_runner.py index 5c6cecd..4e6c1b0 100644 --- a/packages/agentic-fabric/tests/test_core_runner.py +++ b/packages/agentic-fabric/tests/test_core_runner.py @@ -1,4 +1,4 @@ -"""Tests for the legacy CrewAI runner facade.""" +"""Tests for the direct fabric-agent runner facade.""" from __future__ import annotations @@ -7,7 +7,7 @@ import pytest -from agentic_fabric.core import runner as crew_runner +from agentic_fabric.core import runner as fabric_runner class FakeRawResult: @@ -24,7 +24,7 @@ def __str__(self) -> str: class FakeCrew: - """Minimal crew object with kickoff capture.""" + """Minimal CrewAI-like object with kickoff capture.""" def __init__(self, result: Any) -> None: self.result = result @@ -35,58 +35,58 @@ def kickoff(self, inputs: dict[str, Any]) -> Any: return self.result -def test_run_crew_discovers_package_loads_config_and_returns_raw(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +def test_run_fabric_agent_discovers_package_loads_config_and_returns_raw(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: crewai_dir = tmp_path / "pkg" / ".crewai" fake_crew = FakeCrew(FakeRawResult()) calls: list[tuple[Any, ...]] = [] - monkeypatch.setattr(crew_runner, "discover_packages", lambda workspace_root: {"pkg": crewai_dir}) + monkeypatch.setattr(fabric_runner, "discover_packages", lambda workspace_root: {"pkg": crewai_dir}) - def fake_get_crew_config(path: Path, crew_name: str) -> dict[str, Any]: - calls.append(("config", path, crew_name)) - return {"name": crew_name} + def fake_get_fabric_agent_config(path: Path, fabric_agent_name: str) -> dict[str, Any]: + calls.append(("config", path, fabric_agent_name)) + return {"name": fabric_agent_name} - def fake_load_crew_from_config(config: dict[str, Any]) -> FakeCrew: + def fake_load_fabric_agent_from_config(config: dict[str, Any]) -> FakeCrew: calls.append(("load", config)) return fake_crew - monkeypatch.setattr(crew_runner, "get_crew_config", fake_get_crew_config) - monkeypatch.setattr(crew_runner, "load_crew_from_config", fake_load_crew_from_config) + monkeypatch.setattr(fabric_runner, "get_fabric_agent_config", fake_get_fabric_agent_config) + monkeypatch.setattr(fabric_runner, "load_fabric_agent_from_config", fake_load_fabric_agent_from_config) - result = crew_runner.run_crew("pkg", "builder", inputs={"topic": "tests"}, workspace_root=tmp_path) + result = fabric_runner.run_fabric_agent("pkg", "builder", inputs={"topic": "tests"}, workspace_root=tmp_path) assert result == "raw output" assert fake_crew.inputs == {"topic": "tests"} assert calls == [("config", crewai_dir, "builder"), ("load", {"name": "builder"})] -def test_run_crew_reports_available_packages(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(crew_runner, "discover_packages", lambda workspace_root: {"known": Path(".crewai")}) +def test_run_fabric_agent_reports_available_packages(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(fabric_runner, "discover_packages", lambda workspace_root: {"known": Path(".crewai")}) with pytest.raises(ValueError, match=r"Package 'missing' not found. Available: \['known'\]"): - crew_runner.run_crew("missing", "builder") + fabric_runner.run_fabric_agent("missing", "builder") -def test_run_crew_defaults_inputs_to_empty_dict(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +def test_run_fabric_agent_defaults_inputs_to_empty_dict(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: fake_crew = FakeCrew(FakeStringResult()) - monkeypatch.setattr(crew_runner, "discover_packages", lambda workspace_root: {"pkg": tmp_path}) - monkeypatch.setattr(crew_runner, "get_crew_config", lambda path, crew_name: {"name": crew_name}) - monkeypatch.setattr(crew_runner, "load_crew_from_config", lambda config: fake_crew) + monkeypatch.setattr(fabric_runner, "discover_packages", lambda workspace_root: {"pkg": tmp_path}) + monkeypatch.setattr(fabric_runner, "get_fabric_agent_config", lambda path, fabric_agent_name: {"name": fabric_agent_name}) + monkeypatch.setattr(fabric_runner, "load_fabric_agent_from_config", lambda config: fake_crew) - result = crew_runner.run_crew("pkg", "builder") + result = fabric_runner.run_fabric_agent("pkg", "builder") assert result == "string output" assert fake_crew.inputs == {} -def test_run_crew_from_path_loads_direct_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: +def test_run_fabric_agent_from_path_loads_direct_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: fake_crew = FakeCrew(FakeStringResult()) - monkeypatch.setattr(crew_runner, "get_crew_config", lambda path, crew_name: {"path": path, "name": crew_name}) - monkeypatch.setattr(crew_runner, "load_crew_from_config", lambda config: fake_crew) + monkeypatch.setattr(fabric_runner, "get_fabric_agent_config", lambda path, fabric_agent_name: {"path": path, "name": fabric_agent_name}) + monkeypatch.setattr(fabric_runner, "load_fabric_agent_from_config", lambda config: fake_crew) - result = crew_runner.run_crew_from_path(tmp_path / ".crewai", "builder", inputs={"x": 1}) + result = fabric_runner.run_fabric_agent_from_path(tmp_path / ".crewai", "builder", inputs={"x": 1}) assert result == "string output" assert fake_crew.inputs == {"x": 1} diff --git a/packages/agentic-fabric/tests/test_crew_mocker.py b/packages/agentic-fabric/tests/test_crew_mocker.py deleted file mode 100644 index 16a2d44..0000000 --- a/packages/agentic-fabric/tests/test_crew_mocker.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Tests for local agentic-fabric test support.""" - -from __future__ import annotations - -import sys - -from pathlib import Path -from typing import TYPE_CHECKING - -from tests._crew_mocker import ALL_FRAMEWORK_MODULES, CREWAI_MODULES, LANGGRAPH_MODULES, STRANDS_MODULES - - -if TYPE_CHECKING: - from tests._crew_mocker import CrewMocker - - -class TestCrewMockerBasics: - """Test basic CrewMocker functionality.""" - - def test_crew_mocker_has_mocker(self, crew_mocker: CrewMocker) -> None: - assert crew_mocker.mocker is not None - - def test_magic_mock_property(self, crew_mocker: CrewMocker) -> None: - assert crew_mocker.MagicMock() is not None - - def test_mock_module_adds_to_sys_modules(self, crew_mocker: CrewMocker) -> None: - mock = crew_mocker.mock_module("test_fake_module_xyz") - assert sys.modules["test_fake_module_xyz"] is mock - - def test_mock_module_returns_same_mock_on_repeat(self, crew_mocker: CrewMocker) -> None: - assert crew_mocker.mock_module("test_fake_repeat") is crew_mocker.mock_module("test_fake_repeat") - - def test_restore_modules_cleans_up(self, crew_mocker: CrewMocker) -> None: - crew_mocker.mock_module("test_restore_target") - crew_mocker.restore_modules() - assert "test_restore_target" not in sys.modules - - -class TestFrameworkMocking: - """Test framework-specific mocking helpers.""" - - def test_mock_crewai_mocks_all_modules(self, crew_mocker: CrewMocker) -> None: - mocks = crew_mocker.mock_crewai() - for module in CREWAI_MODULES: - assert module in mocks - assert module in sys.modules - - def test_mock_langgraph_mocks_all_modules(self, crew_mocker: CrewMocker) -> None: - mocks = crew_mocker.mock_langgraph() - for module in LANGGRAPH_MODULES: - assert module in mocks - - def test_mock_strands_mocks_all_modules(self, crew_mocker: CrewMocker) -> None: - mocks = crew_mocker.mock_strands() - for module in STRANDS_MODULES: - assert module in mocks - - def test_mock_all_frameworks(self, crew_mocker: CrewMocker) -> None: - mocks = crew_mocker.mock_all_frameworks() - for module in ALL_FRAMEWORK_MODULES: - assert module in mocks - - def test_patch_get_llm_custom_return(self, crew_mocker: CrewMocker) -> None: - custom_llm = crew_mocker.MagicMock(name="custom_llm") - mock = crew_mocker.patch_get_llm(return_value=custom_llm) - assert mock.return_value is custom_llm - - -class TestLocalFixtures: - """Test reusable local fixtures.""" - - def test_temp_workspace_fixture(self, temp_workspace: Path) -> None: - assert (temp_workspace / "packages" / "sample" / ".crewai" / "manifest.yaml").exists() diff --git a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py index bef74aa..fdb3b29 100644 --- a/packages/agentic-fabric/tests/test_decomposer_edge_cases.py +++ b/packages/agentic-fabric/tests/test_decomposer_edge_cases.py @@ -10,14 +10,14 @@ _framework_cache, _get_install_command, _resolve_required_framework, - decompose_crew, + compose_fabric_agent, detect_framework, get_available_frameworks, get_framework_info, get_runner, is_cli_runner_available, is_framework_available, - run_crew_auto, + run_fabric_agent_auto, ) from agentic_fabric.runners.registry import clear_runtime_cache @@ -218,72 +218,72 @@ def is_available(self) -> bool: ) assert is_cli_runner_available("missing") is False - def test_decompose_crew_enforces_required_runtime_and_builds( + def test_compose_fabric_agent_enforces_required_runtime_and_builds( self, monkeypatch: pytest.MonkeyPatch, ) -> None: """Required framework configs should build through that runtime.""" class FakeRunner: - def build_crew(self, crew_config): - return {"built": crew_config} + def build_fabric_agent(self, fabric_agent_config): + return {"built": fabric_agent_config} monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda framework: True) monkeypatch.setattr("agentic_fabric.core.decomposer.get_runner", lambda framework: FakeRunner()) - crew_config = {"required_framework": "crewai", "name": "reviewer"} + fabric_agent_config = {"required_framework": "crewai", "name": "reviewer"} - assert decompose_crew(crew_config, framework="auto") == {"built": crew_config} + assert compose_fabric_agent(fabric_agent_config, framework="auto") == {"built": fabric_agent_config} - def test_decompose_crew_reports_conflicting_or_missing_required_runtime( + def test_compose_fabric_agent_reports_conflicting_or_missing_required_runtime( self, monkeypatch: pytest.MonkeyPatch, ) -> None: """Required runtime errors should surface clear guidance.""" - crew_config = {"required_framework": "crewai"} + fabric_agent_config = {"required_framework": "crewai"} - with pytest.raises(ValueError, match="Crew requires crewai"): - decompose_crew(crew_config, framework="strands") + with pytest.raises(ValueError, match="Fabric agent requires crewai"): + compose_fabric_agent(fabric_agent_config, framework="strands") monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda framework: False) with pytest.raises(RuntimeError, match="pip install crewai"): - decompose_crew(crew_config) + compose_fabric_agent(fabric_agent_config) - def test_run_crew_auto_enforces_required_runtime_and_runs( + def test_run_fabric_agent_auto_enforces_required_runtime_and_runs( self, monkeypatch: pytest.MonkeyPatch, ) -> None: - """run_crew_auto should build and run through the selected runner.""" + """run_fabric_agent_auto should build and run through the selected runner.""" class FakeRunner: - def build_crew(self, crew_config): - return {"crew": crew_config} + def build_fabric_agent(self, fabric_agent_config): + return {"fabric_agent": fabric_agent_config} - def run(self, crew, inputs): - return f"{crew['crew']['name']}:{inputs['task']}" + def run(self, fabric_agent, inputs): + return f"{fabric_agent['fabric_agent']['name']}:{inputs['task']}" monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda framework: True) monkeypatch.setattr("agentic_fabric.core.decomposer.get_runner", lambda framework: FakeRunner()) - result = run_crew_auto({"name": "reviewer", "required_framework": "langgraph"}, {"task": "go"}) + result = run_fabric_agent_auto({"name": "reviewer", "required_framework": "langgraph"}, {"task": "go"}) assert result == "reviewer:go" - def test_run_crew_auto_reports_conflicting_or_missing_required_runtime( + def test_run_fabric_agent_auto_reports_conflicting_or_missing_required_runtime( self, monkeypatch: pytest.MonkeyPatch, ) -> None: - """run_crew_auto should reject conflicts and unavailable required runtimes.""" - crew_config = {"required_framework": "langgraph"} + """run_fabric_agent_auto should reject conflicts and unavailable required runtimes.""" + fabric_agent_config = {"required_framework": "langgraph"} - with pytest.raises(ValueError, match="Crew requires langgraph"): - run_crew_auto(crew_config, framework="crewai") + with pytest.raises(ValueError, match="Fabric agent requires langgraph"): + run_fabric_agent_auto(fabric_agent_config, framework="crewai") monkeypatch.setattr("agentic_fabric.core.decomposer.is_framework_available", lambda framework: False) with pytest.raises(RuntimeError, match=r"agentic-fabric\[langgraph\]"): - run_crew_auto(crew_config) + run_fabric_agent_auto(fabric_agent_config) class TestFrameworkInfo: diff --git a/packages/agentic-fabric/tests/test_discovery.py b/packages/agentic-fabric/tests/test_discovery.py index 87ed326..ab525a8 100644 --- a/packages/agentic-fabric/tests/test_discovery.py +++ b/packages/agentic-fabric/tests/test_discovery.py @@ -20,37 +20,37 @@ def test_discover_packages_finds_crewai_directories(self, temp_workspace: Path) assert "sample" in packages assert packages["sample"].exists() - def test_discover_packages_finds_crew_directories(self, tmp_path: Path) -> None: - """Test that discover_packages finds framework-agnostic .crew directories.""" + def test_discover_packages_finds_fabric_directories(self, tmp_path: Path) -> None: + """Test that discover_packages finds framework-agnostic .fabric directories.""" from agentic_fabric.core.discovery import discover_packages - # Create packages with .crew directory + # Create packages with .fabric directory pkg_dir = tmp_path / "packages" / "strata" - crew_dir = pkg_dir / ".crew" - crew_dir.mkdir(parents=True) - (crew_dir / "manifest.yaml").write_text("name: strata\ncrews: {}") + fabric_dir = pkg_dir / ".fabric" + fabric_dir.mkdir(parents=True) + (fabric_dir / "manifest.yaml").write_text("name: strata\nfabric_agents: {}") packages = discover_packages(workspace_root=tmp_path) assert "strata" in packages - assert packages["strata"].name == ".crew" + assert packages["strata"].name == ".fabric" - def test_discover_packages_prefers_crew_over_crewai(self, tmp_path: Path) -> None: - """Test that .crew takes priority over .crewai when both exist.""" + def test_discover_packages_prefers_fabric_over_crewai(self, tmp_path: Path) -> None: + """Test that .fabric takes priority over .crewai when both exist.""" from agentic_fabric.core.discovery import discover_packages - # Create package with both .crew and .crewai + # Create package with both .fabric and .crewai pkg_dir = tmp_path / "packages" / "hybrid" - (pkg_dir / ".crew").mkdir(parents=True) - (pkg_dir / ".crew" / "manifest.yaml").write_text("name: hybrid\ncrews: {}") + (pkg_dir / ".fabric").mkdir(parents=True) + (pkg_dir / ".fabric" / "manifest.yaml").write_text("name: hybrid\nfabric_agents: {}") (pkg_dir / ".crewai").mkdir(parents=True) - (pkg_dir / ".crewai" / "manifest.yaml").write_text("name: hybrid\ncrews: {}") + (pkg_dir / ".crewai" / "manifest.yaml").write_text("name: hybrid\nfabric_agents: {}") packages = discover_packages(workspace_root=tmp_path) assert "hybrid" in packages - # .crew should be preferred (framework-agnostic first) - assert packages["hybrid"].name == ".crew" + # .fabric should be preferred (framework-agnostic first) + assert packages["hybrid"].name == ".fabric" def test_discover_packages_returns_empty_when_no_packages(self, tmp_path: Path) -> None: """Test that discover_packages returns empty dict when no config dirs exist.""" @@ -71,15 +71,15 @@ def test_discover_all_framework_configs(self, tmp_path: Path) -> None: # Create package with multiple framework configs pkg_dir = tmp_path / "packages" / "multi" - (pkg_dir / ".crew").mkdir(parents=True) - (pkg_dir / ".crew" / "manifest.yaml").write_text("name: multi\ncrews: {}") + (pkg_dir / ".fabric").mkdir(parents=True) + (pkg_dir / ".fabric" / "manifest.yaml").write_text("name: multi\nfabric_agents: {}") (pkg_dir / ".crewai").mkdir(parents=True) - (pkg_dir / ".crewai" / "manifest.yaml").write_text("name: multi\ncrews: {}") + (pkg_dir / ".crewai" / "manifest.yaml").write_text("name: multi\nfabric_agents: {}") configs = discover_all_framework_configs(workspace_root=tmp_path) assert "multi" in configs - assert None in configs["multi"] # .crew -> None (agnostic) + assert None in configs["multi"] # .fabric -> None (agnostic) assert "crewai" in configs["multi"] def test_discover_all_framework_configs_uses_default_root_and_skips_files( @@ -95,60 +95,60 @@ def test_discover_all_framework_configs_uses_default_root_and_skips_files( (packages_dir / "README.md").write_text("not a package", encoding="utf-8") pkg_dir = packages_dir / "multi" - (pkg_dir / ".crew").mkdir(parents=True) - (pkg_dir / ".crew" / "manifest.yaml").write_text("name: multi\ncrews: {}\n", encoding="utf-8") + (pkg_dir / ".fabric").mkdir(parents=True) + (pkg_dir / ".fabric" / "manifest.yaml").write_text("name: multi\nfabric_agents: {}\n", encoding="utf-8") (pkg_dir / ".strands").mkdir() - (pkg_dir / ".strands" / "manifest.yaml").write_text("name: multi\ncrews: {}\n", encoding="utf-8") + (pkg_dir / ".strands" / "manifest.yaml").write_text("name: multi\nfabric_agents: {}\n", encoding="utf-8") (tmp_path / ".langgraph").mkdir() - (tmp_path / ".langgraph" / "manifest.yaml").write_text("name: root\ncrews: {}\n", encoding="utf-8") + (tmp_path / ".langgraph" / "manifest.yaml").write_text("name: root\nfabric_agents: {}\n", encoding="utf-8") monkeypatch.setattr("agentic_fabric.core.discovery.get_workspace_root", lambda: tmp_path) configs = discover_all_framework_configs() - assert configs["multi"][None] == pkg_dir / ".crew" + assert configs["multi"][None] == pkg_dir / ".fabric" assert configs["multi"]["strands"] == pkg_dir / ".strands" assert configs[tmp_path.name]["langgraph"] == tmp_path / ".langgraph" - def test_list_crews_returns_crews_from_manifest(self, temp_workspace: Path) -> None: - """Test that list_crews returns crew definitions from manifest.""" - from agentic_fabric.core.discovery import list_crews + def test_list_fabric_agents_returns_entries_from_manifest(self, temp_workspace: Path) -> None: + """Test that list_fabric_agents returns fabric agent definitions from manifest.""" + from agentic_fabric.core.discovery import list_fabric_agents with patch( "agentic_fabric.core.discovery.discover_packages", return_value={"sample": temp_workspace / "packages" / "sample" / ".crewai"}, ): - crews_by_package = list_crews() + fabric_agents_by_package = list_fabric_agents() - assert "sample" in crews_by_package - crews = crews_by_package["sample"] - assert len(crews) == 1 - assert crews[0]["name"] == "test_crew" + assert "sample" in fabric_agents_by_package + fabric_agents = fabric_agents_by_package["sample"] + assert len(fabric_agents) == 1 + assert fabric_agents[0]["name"] == "test_fabric_agent" - def test_list_crews_filters_by_package_name(self, temp_workspace: Path) -> None: - """Test that list_crews can filter to a specific package.""" - from agentic_fabric.core.discovery import list_crews + def test_list_fabric_agents_filters_by_package_name(self, temp_workspace: Path) -> None: + """Test that list_fabric_agents can filter to a specific package.""" + from agentic_fabric.core.discovery import list_fabric_agents with patch( "agentic_fabric.core.discovery.discover_packages", return_value={"sample": temp_workspace / "packages" / "sample" / ".crewai"}, ): - crews_by_package = list_crews(package_name="sample") + fabric_agents_by_package = list_fabric_agents(package_name="sample") - assert "sample" in crews_by_package - assert len(crews_by_package) == 1 + assert "sample" in fabric_agents_by_package + assert len(fabric_agents_by_package) == 1 - def test_list_crews_returns_empty_for_nonexistent_package(self, temp_workspace: Path) -> None: - """Test that list_crews returns empty for non-existent package.""" - from agentic_fabric.core.discovery import list_crews + def test_list_fabric_agents_returns_empty_for_nonexistent_package(self, temp_workspace: Path) -> None: + """Test that list_fabric_agents returns empty for non-existent package.""" + from agentic_fabric.core.discovery import list_fabric_agents with patch( "agentic_fabric.core.discovery.discover_packages", return_value={"sample": temp_workspace / "packages" / "sample" / ".crewai"}, ): - crews_by_package = list_crews(package_name="nonexistent") + fabric_agents_by_package = list_fabric_agents(package_name="nonexistent") - assert crews_by_package == {} + assert fabric_agents_by_package == {} def test_load_manifest_parses_yaml(self, temp_workspace: Path) -> None: """Test that load_manifest parses YAML correctly.""" @@ -159,7 +159,7 @@ def test_load_manifest_parses_yaml(self, temp_workspace: Path) -> None: assert manifest is not None assert manifest.get("name") == "sample" - assert "crews" in manifest + assert "fabric_agents" in manifest def test_get_workspace_root_finds_root(self) -> None: """Test that get_workspace_root finds the workspace root.""" @@ -187,30 +187,30 @@ def test_get_framework_from_config_dir(self) -> None: """Test framework detection from directory name.""" from agentic_fabric.core.discovery import get_framework_from_config_dir - assert get_framework_from_config_dir(Path("/some/path/.crew")) is None + assert get_framework_from_config_dir(Path("/some/path/.fabric")) is None assert get_framework_from_config_dir(Path("/some/path/.crewai")) == "crewai" assert get_framework_from_config_dir(Path("/some/path/.langgraph")) == "langgraph" assert get_framework_from_config_dir(Path("/some/path/.strands")) == "strands" - def test_get_crew_config_includes_required_framework(self, temp_workspace: Path) -> None: - """Test that get_crew_config includes required_framework field.""" - from agentic_fabric.core.discovery import get_crew_config + def test_get_fabric_agent_config_includes_required_framework(self, temp_workspace: Path) -> None: + """Test that get_fabric_agent_config includes required_framework field.""" + from agentic_fabric.core.discovery import get_fabric_agent_config crewai_dir = temp_workspace / "packages" / "sample" / ".crewai" - config = get_crew_config(crewai_dir, "test_crew") + config = get_fabric_agent_config(crewai_dir, "test_fabric_agent") assert config["required_framework"] == "crewai" - def test_get_crew_config_requires_agents_and_tasks_keys(self, tmp_path: Path) -> None: - """Crew configs should fail before resolving missing YAML paths.""" - from agentic_fabric.core.discovery import get_crew_config + def test_get_fabric_agent_config_requires_agents_and_tasks_keys(self, tmp_path: Path) -> None: + """Fabric agent configs should fail before resolving missing YAML paths.""" + from agentic_fabric.core.discovery import get_fabric_agent_config - crew_dir = tmp_path / ".crew" - crew_dir.mkdir() - (crew_dir / "manifest.yaml").write_text( + fabric_dir = tmp_path / ".fabric" + fabric_dir.mkdir() + (fabric_dir / "manifest.yaml").write_text( """ name: missing-files -crews: +fabric_agents: no_agents: tasks: tasks.yaml no_tasks: @@ -220,10 +220,10 @@ def test_get_crew_config_requires_agents_and_tasks_keys(self, tmp_path: Path) -> ) with pytest.raises(ValueError, match="missing required key: agents"): - get_crew_config(crew_dir, "no_agents") + get_fabric_agent_config(fabric_dir, "no_agents") with pytest.raises(ValueError, match="missing required key: tasks"): - get_crew_config(crew_dir, "no_tasks") + get_fabric_agent_config(fabric_dir, "no_tasks") class TestDecomposer: @@ -435,14 +435,14 @@ def test_get_runner_raises_for_unknown_framework(self) -> None: with pytest.raises(ValueError, match="Unknown framework"): get_runner("unknown_framework") - def test_decompose_crew_uses_required_framework(self, tmp_path: Path) -> None: - """Test that decompose_crew respects required_framework from config.""" + def test_compose_fabric_agent_uses_required_framework(self, tmp_path: Path) -> None: + """Test that compose_fabric_agent respects required_framework from config.""" from unittest.mock import MagicMock - from agentic_fabric.core.decomposer import decompose_crew + from agentic_fabric.core.decomposer import compose_fabric_agent - crew_config = { - "name": "test_crew", + fabric_agent_config = { + "name": "test_fabric_agent", "required_framework": "strands", "agents": {}, "tasks": {}, @@ -450,7 +450,7 @@ def test_decompose_crew_uses_required_framework(self, tmp_path: Path) -> None: mock_runner = MagicMock() mock_runner.framework_name = "strands" - mock_runner.build_crew.return_value = MagicMock() + mock_runner.build_fabric_agent.return_value = MagicMock() def mock_available(framework): return framework in ["crewai", "strands"] @@ -465,18 +465,18 @@ def mock_available(framework): return_value=mock_runner, ), ): - # decompose_crew returns the result of runner.build_crew() - decompose_crew(crew_config) + # compose_fabric_agent returns the result of runner.build_fabric_agent() + compose_fabric_agent(fabric_agent_config) - # Verify the runner's build_crew was called - mock_runner.build_crew.assert_called_once_with(crew_config) + # Verify the runner's build_fabric_agent was called + mock_runner.build_fabric_agent.assert_called_once_with(fabric_agent_config) - def test_decompose_crew_raises_when_required_unavailable(self) -> None: - """Test decompose_crew raises when required framework not available.""" - from agentic_fabric.core.decomposer import decompose_crew + def test_compose_fabric_agent_raises_when_required_unavailable(self) -> None: + """Test compose_fabric_agent raises when required framework not available.""" + from agentic_fabric.core.decomposer import compose_fabric_agent - crew_config = { - "name": "test_crew", + fabric_agent_config = { + "name": "test_fabric_agent", "required_framework": "langgraph", "agents": {}, "tasks": {}, @@ -489,14 +489,14 @@ def test_decompose_crew_raises_when_required_unavailable(self) -> None: ), pytest.raises(RuntimeError, match=r"requires langgraph.*not installed"), ): - decompose_crew(crew_config) + compose_fabric_agent(fabric_agent_config) - def test_decompose_crew_validates_framework_conflict(self) -> None: - """Test decompose_crew validates requested vs required conflict.""" - from agentic_fabric.core.decomposer import decompose_crew + def test_compose_fabric_agent_validates_framework_conflict(self) -> None: + """Test compose_fabric_agent validates requested vs required conflict.""" + from agentic_fabric.core.decomposer import compose_fabric_agent - crew_config = { - "name": "test_crew", + fabric_agent_config = { + "name": "test_fabric_agent", "required_framework": "crewai", "agents": {}, "tasks": {}, @@ -509,7 +509,7 @@ def test_decompose_crew_validates_framework_conflict(self) -> None: ), pytest.raises(ValueError, match=r"requires crewai.*langgraph was requested"), ): - decompose_crew(crew_config, framework="langgraph") + compose_fabric_agent(fabric_agent_config, framework="langgraph") def test_get_install_command_returns_pip_install(self) -> None: """Test that _get_install_command returns correct pip command.""" diff --git a/packages/agentic-fabric/tests/test_discovery_nested.py b/packages/agentic-fabric/tests/test_discovery_nested.py index a980889..75617cf 100644 --- a/packages/agentic-fabric/tests/test_discovery_nested.py +++ b/packages/agentic-fabric/tests/test_discovery_nested.py @@ -1,4 +1,4 @@ -"""Tests for crew discovery in nested directories and edge cases.""" +"""Tests for fabric_agent discovery in nested directories and edge cases.""" from __future__ import annotations @@ -26,35 +26,35 @@ def test_skips_non_directory_entries(self, tmp_path: Path) -> None: # Create a real package too pkg_dir = packages_dir / "real_pkg" - crew_dir = pkg_dir / ".crew" - crew_dir.mkdir(parents=True) - (crew_dir / "manifest.yaml").write_text("name: real\ncrews: {}") + fabric_dir = pkg_dir / ".fabric" + fabric_dir.mkdir(parents=True) + (fabric_dir / "manifest.yaml").write_text("name: real\nfabric_agents: {}") packages = discover_packages(workspace_root=tmp_path) assert "real_pkg" in packages assert "README.md" not in packages def test_standalone_project_at_root(self, tmp_path: Path) -> None: - """A .crew directory at workspace root should be discovered.""" - crew_dir = tmp_path / ".crew" - crew_dir.mkdir() - (crew_dir / "manifest.yaml").write_text("name: root_project\ncrews: {}") + """A .fabric directory at workspace root should be discovered.""" + fabric_dir = tmp_path / ".fabric" + fabric_dir.mkdir() + (fabric_dir / "manifest.yaml").write_text("name: root_project\nfabric_agents: {}") packages = discover_packages(workspace_root=tmp_path) assert tmp_path.name in packages def test_standalone_and_packages_coexist(self, tmp_path: Path) -> None: - """Root .crew and packages/.crew should both be discovered.""" - # Root level .crew - root_crew = tmp_path / ".crew" - root_crew.mkdir() - (root_crew / "manifest.yaml").write_text("name: root\ncrews: {}") + """Root .fabric and packages/.fabric should both be discovered.""" + # Root level .fabric + root_fabric = tmp_path / ".fabric" + root_fabric.mkdir() + (root_fabric / "manifest.yaml").write_text("name: root\nfabric_agents: {}") - # Package level .crew + # Package level .fabric pkg_dir = tmp_path / "packages" / "sub_pkg" - pkg_crew = pkg_dir / ".crew" - pkg_crew.mkdir(parents=True) - (pkg_crew / "manifest.yaml").write_text("name: sub\ncrews: {}") + pkg_fabric = pkg_dir / ".fabric" + pkg_fabric.mkdir(parents=True) + (pkg_fabric / "manifest.yaml").write_text("name: sub\nfabric_agents: {}") packages = discover_packages(workspace_root=tmp_path) assert "sub_pkg" in packages @@ -77,14 +77,14 @@ def test_framework_filter_crewai(self, tmp_path: Path) -> None: """Filtering by framework='crewai' should only find .crewai dirs.""" pkg_dir = tmp_path / "packages" / "test_pkg" - # Create .crew (agnostic) and .crewai (specific) - crew_dir = pkg_dir / ".crew" - crew_dir.mkdir(parents=True) - (crew_dir / "manifest.yaml").write_text("name: test\ncrews: {}") + # Create .fabric (agnostic) and .crewai (specific) + fabric_dir = pkg_dir / ".fabric" + fabric_dir.mkdir(parents=True) + (fabric_dir / "manifest.yaml").write_text("name: test\nfabric_agents: {}") crewai_dir = pkg_dir / ".crewai" crewai_dir.mkdir() - (crewai_dir / "manifest.yaml").write_text("name: test\ncrews: {}") + (crewai_dir / "manifest.yaml").write_text("name: test\nfabric_agents: {}") packages = discover_packages(workspace_root=tmp_path, framework="crewai") assert "test_pkg" in packages @@ -93,9 +93,9 @@ def test_framework_filter_crewai(self, tmp_path: Path) -> None: def test_framework_filter_no_match(self, tmp_path: Path) -> None: """Filtering by framework with no matching dirs returns empty.""" pkg_dir = tmp_path / "packages" / "test_pkg" - crew_dir = pkg_dir / ".crewai" - crew_dir.mkdir(parents=True) - (crew_dir / "manifest.yaml").write_text("name: test\ncrews: {}") + fabric_dir = pkg_dir / ".crewai" + fabric_dir.mkdir(parents=True) + (fabric_dir / "manifest.yaml").write_text("name: test\nfabric_agents: {}") packages = discover_packages(workspace_root=tmp_path, framework="strands") assert packages == {} @@ -114,7 +114,7 @@ def test_file_shaped_config_path_is_skipped(self, tmp_path: Path) -> None: """A file named like a config directory should not be discovered.""" pkg_dir = tmp_path / "packages" / "test_pkg" pkg_dir.mkdir(parents=True) - (pkg_dir / ".crew").write_text("not a directory", encoding="utf-8") + (pkg_dir / ".fabric").write_text("not a directory", encoding="utf-8") (tmp_path / ".langgraph").write_text("not a directory", encoding="utf-8") packages = discover_packages(workspace_root=tmp_path) @@ -125,9 +125,9 @@ def test_multiple_packages_discovered(self, tmp_path: Path) -> None: """Multiple packages should all be discovered.""" packages_dir = tmp_path / "packages" for name in ["alpha", "beta", "gamma"]: - crew_dir = packages_dir / name / ".crew" - crew_dir.mkdir(parents=True) - (crew_dir / "manifest.yaml").write_text(f"name: {name}\ncrews: {{}}") + fabric_dir = packages_dir / name / ".fabric" + fabric_dir.mkdir(parents=True) + (fabric_dir / "manifest.yaml").write_text(f"name: {name}\nfabric_agents: {{}}") packages = discover_packages(workspace_root=tmp_path) assert set(packages.keys()) == {"alpha", "beta", "gamma"} @@ -137,26 +137,26 @@ class TestDiscoverAllFrameworkConfigs: """Test discover_all_framework_configs.""" def test_multiple_frameworks_per_package(self, tmp_path: Path) -> None: - """A package with .crew, .crewai, and .strands should return all.""" + """A package with .fabric, .crewai, and .strands should return all.""" pkg_dir = tmp_path / "packages" / "multi" - for dir_name in [".crew", ".crewai", ".strands"]: + for dir_name in [".fabric", ".crewai", ".strands"]: fw_dir = pkg_dir / dir_name fw_dir.mkdir(parents=True) - (fw_dir / "manifest.yaml").write_text(f"name: multi\nframework_dir: {dir_name}\ncrews: {{}}") + (fw_dir / "manifest.yaml").write_text(f"name: multi\nframework_dir: {dir_name}\nfabric_agents: {{}}") configs = discover_all_framework_configs(workspace_root=tmp_path) assert "multi" in configs - assert None in configs["multi"] # .crew -> agnostic + assert None in configs["multi"] # .fabric -> agnostic assert "crewai" in configs["multi"] assert "strands" in configs["multi"] def test_root_configs_discovered(self, tmp_path: Path) -> None: """Root-level framework configs should be discovered.""" - crew_dir = tmp_path / ".langgraph" - crew_dir.mkdir() - (crew_dir / "manifest.yaml").write_text("name: root\ncrews: {}") + fabric_dir = tmp_path / ".langgraph" + fabric_dir.mkdir() + (fabric_dir / "manifest.yaml").write_text("name: root\nfabric_agents: {}") configs = discover_all_framework_configs(workspace_root=tmp_path) assert tmp_path.name in configs diff --git a/packages/agentic-fabric/tests/test_examples.py b/packages/agentic-fabric/tests/test_examples.py index 1597f06..19965a9 100644 --- a/packages/agentic-fabric/tests/test_examples.py +++ b/packages/agentic-fabric/tests/test_examples.py @@ -21,17 +21,17 @@ def test_discovery_workflow_summary_uses_bundled_workspace() -> None: assert sorted(summary["packages"]) == ["review"] review = summary["packages"]["review"] - assert review["config_dir"] == ".crew" - crew = review["crews"]["implementation_review"] - assert crew["required_framework"] is None - assert crew["agents"] == ["documenter", "reviewer"] - assert crew["tasks"] == ["review_code", "review_docs"] + assert review["config_dir"] == ".fabric" + fabric_agent = review["fabric_agents"]["implementation_review"] + assert fabric_agent["required_framework"] is None + assert fabric_agent["agents"] == ["documenter", "reviewer"] + assert fabric_agent["tasks"] == ["review_code", "review_docs"] def test_tool_registry_example_inspects_core_registry() -> None: summary = inspect_registry() - assert summary["framework_dirs"] == [".crew", ".crewai", ".langgraph", ".strands"] + assert summary["framework_dirs"] == [".fabric", ".crewai", ".langgraph", ".strands"] assert summary["mcp_git_available"] is False @@ -39,7 +39,7 @@ def test_runtime_context_example_inspects_agentic_data() -> None: summary = inspect_runtime_context() assert summary["active_runtime"] == "crewai" - assert summary["registered_agents"] == ["reviewer"] + assert summary["registered_fabric_agents"] == ["reviewer"] assert summary["runtime_names"] == ["crewai", "langgraph", "strands"] diff --git a/packages/agentic-fabric/tests/test_fabric_mocker.py b/packages/agentic-fabric/tests/test_fabric_mocker.py new file mode 100644 index 0000000..202a55d --- /dev/null +++ b/packages/agentic-fabric/tests/test_fabric_mocker.py @@ -0,0 +1,73 @@ +"""Tests for local agentic-fabric test support.""" + +from __future__ import annotations + +import sys + +from pathlib import Path +from typing import TYPE_CHECKING + +from tests._fabric_mocker import ALL_FRAMEWORK_MODULES, CREWAI_MODULES, LANGGRAPH_MODULES, STRANDS_MODULES + + +if TYPE_CHECKING: + from tests._fabric_mocker import FabricMocker + + +class TestFabricMockerBasics: + """Test basic FabricMocker functionality.""" + + def test_fabric_mocker_has_mocker(self, fabric_mocker: FabricMocker) -> None: + assert fabric_mocker.mocker is not None + + def test_magic_mock_property(self, fabric_mocker: FabricMocker) -> None: + assert fabric_mocker.MagicMock() is not None + + def test_mock_module_adds_to_sys_modules(self, fabric_mocker: FabricMocker) -> None: + mock = fabric_mocker.mock_module("test_fake_module_xyz") + assert sys.modules["test_fake_module_xyz"] is mock + + def test_mock_module_returns_same_mock_on_repeat(self, fabric_mocker: FabricMocker) -> None: + assert fabric_mocker.mock_module("test_fake_repeat") is fabric_mocker.mock_module("test_fake_repeat") + + def test_restore_modules_cleans_up(self, fabric_mocker: FabricMocker) -> None: + fabric_mocker.mock_module("test_restore_target") + fabric_mocker.restore_modules() + assert "test_restore_target" not in sys.modules + + +class TestFrameworkMocking: + """Test framework-specific mocking helpers.""" + + def test_mock_crewai_mocks_all_modules(self, fabric_mocker: FabricMocker) -> None: + mocks = fabric_mocker.mock_crewai() + for module in CREWAI_MODULES: + assert module in mocks + assert module in sys.modules + + def test_mock_langgraph_mocks_all_modules(self, fabric_mocker: FabricMocker) -> None: + mocks = fabric_mocker.mock_langgraph() + for module in LANGGRAPH_MODULES: + assert module in mocks + + def test_mock_strands_mocks_all_modules(self, fabric_mocker: FabricMocker) -> None: + mocks = fabric_mocker.mock_strands() + for module in STRANDS_MODULES: + assert module in mocks + + def test_mock_all_frameworks(self, fabric_mocker: FabricMocker) -> None: + mocks = fabric_mocker.mock_all_frameworks() + for module in ALL_FRAMEWORK_MODULES: + assert module in mocks + + def test_patch_get_llm_custom_return(self, fabric_mocker: FabricMocker) -> None: + custom_llm = fabric_mocker.MagicMock(name="custom_llm") + mock = fabric_mocker.patch_get_llm(return_value=custom_llm) + assert mock.return_value is custom_llm + + +class TestLocalFixtures: + """Test reusable local fixtures.""" + + def test_temp_workspace_fixture(self, temp_workspace: Path) -> None: + assert (temp_workspace / "packages" / "sample" / ".crewai" / "manifest.yaml").exists() diff --git a/packages/agentic-fabric/tests/test_import_surfaces.py b/packages/agentic-fabric/tests/test_import_surfaces.py index 8e9ae92..2d9b1d3 100644 --- a/packages/agentic-fabric/tests/test_import_surfaces.py +++ b/packages/agentic-fabric/tests/test_import_surfaces.py @@ -58,4 +58,4 @@ def test_cli_script_entrypoint_prints_help(monkeypatch: pytest.MonkeyPatch, caps runpy.run_path(str(cli_main.__file__), run_name="__main__") - assert "agentic-fabric - Framework-Agnostic Crew Runner" in capsys.readouterr().out + assert "agentic-fabric - framework-agnostic fabric agent runner" in capsys.readouterr().out diff --git a/packages/agentic-fabric/tests/test_loader.py b/packages/agentic-fabric/tests/test_loader.py index c4cb471..4836f29 100644 --- a/packages/agentic-fabric/tests/test_loader.py +++ b/packages/agentic-fabric/tests/test_loader.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: - from tests._crew_mocker import CrewMocker + from tests._fabric_mocker import FabricMocker pytest.importorskip("crewai", reason="crewai not installed") @@ -22,7 +22,7 @@ class TestCreateAgentFromConfig: """Tests for create_agent_from_config function.""" - def test_creates_agent_with_config(self, crew_mocker: CrewMocker) -> None: + def test_creates_agent_with_config(self, fabric_mocker: FabricMocker) -> None: """Test that create_agent_from_config creates an Agent.""" from agentic_fabric.core.loader import create_agent_from_config @@ -32,8 +32,8 @@ def test_creates_agent_with_config(self, crew_mocker: CrewMocker) -> None: "backstory": "Test backstory", } - MockAgent = crew_mocker.patch("crewai.Agent") - crew_mocker.patch_get_llm() + MockAgent = fabric_mocker.patch("crewai.Agent") + fabric_mocker.patch_get_llm() create_agent_from_config("test_agent", config) @@ -43,14 +43,14 @@ def test_creates_agent_with_config(self, crew_mocker: CrewMocker) -> None: assert call_kwargs["goal"] == "Test goal" assert call_kwargs["backstory"] == "Test backstory" - def test_uses_agent_name_as_default_role(self, crew_mocker: CrewMocker) -> None: + def test_uses_agent_name_as_default_role(self, fabric_mocker: FabricMocker) -> None: """Test that agent name is used as default role.""" from agentic_fabric.core.loader import create_agent_from_config config = {"goal": "Test goal", "backstory": "Test backstory"} - MockAgent = crew_mocker.patch("crewai.Agent") - crew_mocker.patch_get_llm() + MockAgent = fabric_mocker.patch("crewai.Agent") + fabric_mocker.patch_get_llm() create_agent_from_config("custom_agent_name", config) @@ -61,7 +61,7 @@ def test_uses_agent_name_as_default_role(self, crew_mocker: CrewMocker) -> None: class TestCreateTaskFromConfig: """Tests for create_task_from_config function.""" - def test_creates_task_with_config(self, crew_mocker: CrewMocker) -> None: + def test_creates_task_with_config(self, fabric_mocker: FabricMocker) -> None: """Test that create_task_from_config creates a Task.""" from agentic_fabric.core.loader import create_task_from_config @@ -69,9 +69,9 @@ def test_creates_task_with_config(self, crew_mocker: CrewMocker) -> None: "description": "Test task description", "expected_output": "Test output", } - mock_agent = crew_mocker.MagicMock() + mock_agent = fabric_mocker.MagicMock() - MockTask = crew_mocker.patch("crewai.Task") + MockTask = fabric_mocker.patch("crewai.Task") create_task_from_config("test_task", config, mock_agent) @@ -85,7 +85,7 @@ def test_creates_task_with_config(self, crew_mocker: CrewMocker) -> None: class TestLoadKnowledgeSources: """Tests for load_knowledge_sources function.""" - def test_loads_md_files(self, crew_mocker: CrewMocker, tmp_path: Path) -> None: + def test_loads_md_files(self, fabric_mocker: FabricMocker, tmp_path: Path) -> None: """Test that .md files are loaded as knowledge sources.""" from agentic_fabric.core.loader import load_knowledge_sources @@ -94,7 +94,7 @@ def test_loads_md_files(self, crew_mocker: CrewMocker, tmp_path: Path) -> None: knowledge_dir.mkdir() (knowledge_dir / "test.md").write_text("# Test Knowledge\nSome content") - MockKnowledgeSource = crew_mocker.patch_knowledge_source() + MockKnowledgeSource = fabric_mocker.patch_knowledge_source() load_knowledge_sources([knowledge_dir]) @@ -111,14 +111,14 @@ def test_skips_nonexistent_paths(self, tmp_path: Path) -> None: class TestLoadCrewFromConfig: - """Tests for load_crew_from_config function.""" + """Tests for load_fabric_agent_from_config function.""" - def test_creates_crew_with_agents_and_tasks(self, crew_mocker: CrewMocker) -> None: - """Test that load_crew_from_config creates a complete Crew.""" - from agentic_fabric.core.loader import load_crew_from_config + def test_creates_crewai_crew_with_agents_and_tasks(self, fabric_mocker: FabricMocker) -> None: + """Test that load_fabric_agent_from_config creates a complete Crew.""" + from agentic_fabric.core.loader import load_fabric_agent_from_config config = { - "name": "test_crew", + "name": "test_fabric_agent", "agents": { "test_agent": { "role": "Test Agent", @@ -136,19 +136,19 @@ def test_creates_crew_with_agents_and_tasks(self, crew_mocker: CrewMocker) -> No "knowledge_paths": [], } - MockCrew = crew_mocker.patch_crewai_crew() - MockAgent = crew_mocker.patch_crewai_agent() - MockTask = crew_mocker.patch_crewai_task() - crew_mocker.patch_crewai_process() - crew_mocker.patch_get_llm() + MockCrew = fabric_mocker.patch_crewai_crew() + MockAgent = fabric_mocker.patch_crewai_agent() + MockTask = fabric_mocker.patch_crewai_task() + fabric_mocker.patch_crewai_process() + fabric_mocker.patch_get_llm() - mock_agent = crew_mocker.MagicMock() + mock_agent = fabric_mocker.MagicMock() MockAgent.return_value = mock_agent - mock_task = crew_mocker.MagicMock() + mock_task = fabric_mocker.MagicMock() MockTask.return_value = mock_task - load_crew_from_config(config) + load_fabric_agent_from_config(config) # Verify Agent was created MockAgent.assert_called() diff --git a/packages/agentic-fabric/tests/test_loader_unit.py b/packages/agentic-fabric/tests/test_loader_unit.py index 1c87979..040172c 100644 --- a/packages/agentic-fabric/tests/test_loader_unit.py +++ b/packages/agentic-fabric/tests/test_loader_unit.py @@ -147,7 +147,7 @@ def test_create_agent_and_task_from_config(loader_with_fake_crewai: Any, monkeyp assert task.kwargs["agent"] is agent -def test_load_crew_from_config_resolves_tools_and_fallbacks( +def test_load_fabric_agent_from_config_resolves_tools_and_fallbacks( loader_with_fake_crewai: Any, monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -159,7 +159,7 @@ def test_load_crew_from_config_resolves_tools_and_fallbacks( lambda tool_names: ["resolved-tool"] if tool_names else [], ) - crew = loader_with_fake_crewai.load_crew_from_config( + fabric_agent = loader_with_fake_crewai.load_fabric_agent_from_config( { "agents": { "tool_user": {"role": "Tool User", "tools": ["registered"]}, @@ -175,23 +175,23 @@ def test_load_crew_from_config_resolves_tools_and_fallbacks( } ) - assert isinstance(crew, FakeCrew) - assert crew.kwargs["process"] == "sequential" - assert crew.kwargs["planning"] is True - assert crew.kwargs["memory"] is True - assert crew.kwargs["knowledge_sources"] == ["knowledge"] + assert isinstance(fabric_agent, FakeCrew) + assert fabric_agent.kwargs["process"] == "sequential" + assert fabric_agent.kwargs["planning"] is True + assert fabric_agent.kwargs["memory"] is True + assert fabric_agent.kwargs["knowledge_sources"] == ["knowledge"] - agents = crew.kwargs["agents"] + agents = fabric_agent.kwargs["agents"] assert [agent.kwargs["role"] for agent in agents] == ["Tool User", "Engineer", "Researcher"] assert agents[0].kwargs["tools"] == ["resolved-tool"] assert len(agents[1].kwargs["tools"]) == 3 assert len(agents[2].kwargs["tools"]) == 2 - assert len(crew.kwargs["tasks"]) == 3 + assert len(fabric_agent.kwargs["tasks"]) == 3 -def test_load_crew_from_config_requires_task_agent(loader_with_fake_crewai: Any) -> None: +def test_load_fabric_agent_from_config_requires_task_agent(loader_with_fake_crewai: Any) -> None: with pytest.raises(ValueError, match="missing an 'agent' assignment"): - loader_with_fake_crewai.load_crew_from_config( + loader_with_fake_crewai.load_fabric_agent_from_config( { "agents": {"writer": {"role": "Writer"}}, "tasks": {"draft": {"description": "draft"}}, @@ -199,9 +199,9 @@ def test_load_crew_from_config_requires_task_agent(loader_with_fake_crewai: Any) ) -def test_load_crew_from_config_requires_existing_agent(loader_with_fake_crewai: Any) -> None: +def test_load_fabric_agent_from_config_requires_existing_agent(loader_with_fake_crewai: Any) -> None: with pytest.raises(ValueError, match="Agent 'missing' for task 'draft' not found"): - loader_with_fake_crewai.load_crew_from_config( + loader_with_fake_crewai.load_fabric_agent_from_config( { "agents": {"writer": {"role": "Writer"}}, "tasks": {"draft": {"description": "draft", "agent": "missing"}}, diff --git a/packages/agentic-fabric/tests/test_manager.py b/packages/agentic-fabric/tests/test_manager.py index d148caf..28ae0ff 100644 --- a/packages/agentic-fabric/tests/test_manager.py +++ b/packages/agentic-fabric/tests/test_manager.py @@ -15,25 +15,25 @@ class TestManagerAgent: """Tests for ManagerAgent base class.""" - def test_init_with_crews(self): - """Test manager initialization with crew mappings.""" - crews = {"design": "game_design", "qa": "quality_assurance"} - manager = ManagerAgent(crews=crews) + def test_init_with_fabric_agents(self): + """Test manager initialization with fabric agent mappings.""" + fabric_agents = {"design": "game_design", "qa": "quality_assurance"} + manager = ManagerAgent(fabric_agents=fabric_agents) - assert manager.crews == crews + assert manager.fabric_agents == fabric_agents assert manager.package_name is None assert manager.workspace_root is None def test_init_with_package_name(self): """Test manager initialization with package name.""" - crews = {"design": "game_design"} - manager = ManagerAgent(crews=crews, package_name="my_package") + fabric_agents = {"design": "game_design"} + manager = ManagerAgent(fabric_agents=fabric_agents, package_name="my_package") assert manager.package_name == "my_package" def test_get_packages_caches_result(self): """Test that package discovery is cached.""" - manager = ManagerAgent(crews={"test": "test_crew"}) + manager = ManagerAgent(fabric_agents={"test": "test_fabric_agent"}) with patch("agentic_fabric.core.manager.discover_packages") as mock_discover: mock_packages = {"pkg1": Path("/path/pkg1")} @@ -52,7 +52,7 @@ def test_get_packages_caches_result(self): def test_delegate_with_string_input(self): """Test delegation with string input.""" manager = ManagerAgent( - crews={"design": "game_design"}, + fabric_agents={"design": "game_design"}, package_name="test_pkg", ) @@ -65,8 +65,8 @@ def test_delegate_with_string_input(self): with ( patch("agentic_fabric.core.manager.discover_packages") as mock_discover, - patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, - patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + patch("agentic_fabric.core.manager.get_fabric_agent_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_fabric_agent_auto") as mock_run, ): mock_discover.return_value = mock_packages mock_get_config.return_value = mock_config @@ -83,7 +83,7 @@ def test_delegate_with_string_input(self): def test_delegate_with_dict_input(self): """Test delegation with dict input.""" manager = ManagerAgent( - crews={"design": "game_design"}, + fabric_agents={"design": "game_design"}, package_name="test_pkg", ) @@ -92,8 +92,8 @@ def test_delegate_with_dict_input(self): with ( patch("agentic_fabric.core.manager.discover_packages") as mock_discover, - patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, - patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + patch("agentic_fabric.core.manager.get_fabric_agent_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_fabric_agent_auto") as mock_run, ): mock_discover.return_value = mock_packages mock_get_config.return_value = mock_config @@ -109,15 +109,15 @@ def test_delegate_with_dict_input(self): def test_delegate_unknown_role_raises_error(self): """Test that delegating to unknown role raises ValueError.""" - manager = ManagerAgent(crews={"design": "game_design"}) + manager = ManagerAgent(fabric_agents={"design": "game_design"}) - with pytest.raises(ValueError, match="Unknown crew role 'unknown'"): + with pytest.raises(ValueError, match="Unknown fabric agent role 'unknown'"): manager.delegate("unknown", "test task") def test_delegate_package_not_found_raises_error(self): """Test that missing package raises ValueError.""" manager = ManagerAgent( - crews={"design": "game_design"}, + fabric_agents={"design": "game_design"}, package_name="nonexistent", ) @@ -127,9 +127,9 @@ def test_delegate_package_not_found_raises_error(self): with pytest.raises(ValueError, match="Package 'nonexistent' not found"): manager.delegate("design", "test task") - def test_delegate_auto_discover_crew(self): - """Test auto-discovery of crew in packages when package_name not specified.""" - manager = ManagerAgent(crews={"design": "game_design"}) + def test_delegate_auto_discovers_fabric_agent(self): + """Test auto-discovery of a fabric agent when package_name is not specified.""" + manager = ManagerAgent(fabric_agents={"design": "game_design"}) mock_packages = { "pkg1": Path("/pkg1/.crewai"), @@ -139,15 +139,15 @@ def test_delegate_auto_discover_crew(self): with ( patch("agentic_fabric.core.manager.discover_packages") as mock_discover, - patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, - patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + patch("agentic_fabric.core.manager.get_fabric_agent_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_fabric_agent_auto") as mock_run, ): mock_discover.return_value = mock_packages - # First package doesn't have the crew - def get_config_side_effect(pkg_dir, crew_name): + # First package doesn't have the fabric agent. + def get_config_side_effect(pkg_dir, fabric_agent_name): if pkg_dir == Path("/pkg1/.crewai"): - raise ValueError("Crew not found") + raise ValueError("Fabric agent not found") return mock_config mock_get_config.side_effect = get_config_side_effect @@ -159,15 +159,15 @@ def get_config_side_effect(pkg_dir, crew_name): # Should have tried pkg1 (failed), then found in pkg2 (config is cached) assert mock_get_config.call_count == 2 - def test_delegate_uses_cached_crew_config(self): - """Repeated delegation to the same crew should reuse the cached config.""" - manager = ManagerAgent(crews={"design": "game_design"}, package_name="test_pkg") + def test_delegate_uses_cached_fabric_agent_config(self): + """Repeated delegation to the same fabric agent should reuse the cached config.""" + manager = ManagerAgent(fabric_agents={"design": "game_design"}, package_name="test_pkg") mock_config = {"name": "game_design", "agents": {}, "tasks": {}} with ( patch("agentic_fabric.core.manager.discover_packages", return_value={"test_pkg": Path("/test/.crewai")}), - patch("agentic_fabric.core.manager.get_crew_config", return_value=mock_config) as mock_get_config, - patch("agentic_fabric.core.manager.run_crew_auto", side_effect=["first", "second"]) as mock_run, + patch("agentic_fabric.core.manager.get_fabric_agent_config", return_value=mock_config) as mock_get_config, + patch("agentic_fabric.core.manager.run_fabric_agent_auto", side_effect=["first", "second"]) as mock_run, ): assert manager.delegate("design", "first task", framework="crewai") == "first" assert manager.delegate("design", {"task": "second task"}, framework="langgraph") == "second" @@ -175,27 +175,27 @@ def test_delegate_uses_cached_crew_config(self): mock_get_config.assert_called_once_with(Path("/test/.crewai"), "game_design") assert mock_run.call_args_list[1].kwargs == {"inputs": {"task": "second task"}, "framework": "langgraph"} - def test_delegate_crew_not_found_raises_error(self): - """Test that crew not found in any package raises ValueError.""" - manager = ManagerAgent(crews={"design": "nonexistent_crew"}) + def test_delegate_fabric_agent_not_found_raises_error(self): + """Test that fabric agent not found in any package raises ValueError.""" + manager = ManagerAgent(fabric_agents={"design": "missing_fabric_agent"}) mock_packages = {"pkg1": Path("/pkg1/.crewai")} with ( patch("agentic_fabric.core.manager.discover_packages") as mock_discover, - patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, + patch("agentic_fabric.core.manager.get_fabric_agent_config") as mock_get_config, ): mock_discover.return_value = mock_packages - mock_get_config.side_effect = ValueError("Crew not found") + mock_get_config.side_effect = ValueError("Fabric agent not found") - with pytest.raises(ValueError, match="Crew 'nonexistent_crew' not found"): + with pytest.raises(ValueError, match="Fabric agent 'missing_fabric_agent' not found"): manager.delegate("design", "test task") @pytest.mark.asyncio async def test_delegate_async(self): """Test async delegation.""" manager = ManagerAgent( - crews={"design": "game_design"}, + fabric_agents={"design": "game_design"}, package_name="test_pkg", ) @@ -204,8 +204,8 @@ async def test_delegate_async(self): with ( patch("agentic_fabric.core.manager.discover_packages") as mock_discover, - patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, - patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + patch("agentic_fabric.core.manager.get_fabric_agent_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_fabric_agent_auto") as mock_run, ): mock_discover.return_value = mock_packages mock_get_config.return_value = mock_config @@ -217,9 +217,9 @@ async def test_delegate_async(self): @pytest.mark.asyncio async def test_delegate_parallel(self): - """Test parallel delegation to multiple crews.""" + """Test parallel delegation to multiple fabric agents.""" manager = ManagerAgent( - crews={"design": "game_design", "assets": "asset_gen"}, + fabric_agents={"design": "game_design", "assets": "asset_gen"}, package_name="test_pkg", ) @@ -227,25 +227,25 @@ async def test_delegate_parallel(self): with ( patch("agentic_fabric.core.manager.discover_packages") as mock_discover, - patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, - patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + patch("agentic_fabric.core.manager.get_fabric_agent_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_fabric_agent_auto") as mock_run, ): mock_discover.return_value = mock_packages - # Return different configs for different crews to distinguish them - def get_config_side_effect(pkg_dir, crew_name): - return {"name": crew_name, "agents": {}, "tasks": {}} + # Return different configs for different fabric agents to distinguish them. + def get_config_side_effect(pkg_dir, fabric_agent_name): + return {"name": fabric_agent_name, "agents": {}, "tasks": {}} mock_get_config.side_effect = get_config_side_effect - # Mock different results based on crew name in config + # Mock different results based on fabric agent name in config. def run_side_effect(config, inputs, framework=None): - crew_name = config["name"] - if crew_name == "game_design": + fabric_agent_name = config["name"] + if fabric_agent_name == "game_design": return "Design done" - elif crew_name == "asset_gen": + elif fabric_agent_name == "asset_gen": return "Assets done" - return f"Unknown crew: {crew_name}" + return f"Unknown fabric agent: {fabric_agent_name}" mock_run.side_effect = run_side_effect @@ -262,9 +262,9 @@ def run_side_effect(config, inputs, framework=None): assert mock_run.call_count == 2 def test_delegate_sequential(self): - """Test sequential delegation to multiple crews.""" + """Test sequential delegation to multiple fabric agents.""" manager = ManagerAgent( - crews={"design": "game_design", "impl": "implementation"}, + fabric_agents={"design": "game_design", "impl": "implementation"}, package_name="test_pkg", ) @@ -273,8 +273,8 @@ def test_delegate_sequential(self): with ( patch("agentic_fabric.core.manager.discover_packages") as mock_discover, - patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, - patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + patch("agentic_fabric.core.manager.get_fabric_agent_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_fabric_agent_auto") as mock_run, ): mock_discover.return_value = mock_packages mock_get_config.return_value = mock_config @@ -296,7 +296,7 @@ def test_delegate_sequential(self): def test_checkpoint_auto_approve(self): """Test checkpoint with auto_approve=True.""" - manager = ManagerAgent(crews={"test": "test_crew"}) + manager = ManagerAgent(fabric_agents={"test": "test_fabric_agent"}) approved, result = manager.checkpoint( "Review design", @@ -309,7 +309,7 @@ def test_checkpoint_auto_approve(self): def test_checkpoint_base_implementation_auto_approves(self): """Test that base checkpoint implementation auto-approves.""" - manager = ManagerAgent(crews={"test": "test_crew"}) + manager = ManagerAgent(fabric_agents={"test": "test_fabric_agent"}) approved, result = manager.checkpoint( "Review design", @@ -322,7 +322,7 @@ def test_checkpoint_base_implementation_auto_approves(self): def test_execute_workflow_not_implemented(self): """Test that execute_workflow raises NotImplementedError in base class.""" - manager = ManagerAgent(crews={"test": "test_crew"}) + manager = ManagerAgent(fabric_agents={"test": "test_fabric_agent"}) with pytest.raises(NotImplementedError, match="Subclasses must implement"): asyncio.run(manager.execute_workflow("test task")) @@ -342,7 +342,7 @@ async def execute_workflow(self, task: str, **kwargs): return await self.delegate_async("impl", design) manager = TestManager( - crews={"design": "game_design", "impl": "implementation"}, + fabric_agents={"design": "game_design", "impl": "implementation"}, package_name="test_pkg", ) @@ -351,8 +351,8 @@ async def execute_workflow(self, task: str, **kwargs): with ( patch("agentic_fabric.core.manager.discover_packages") as mock_discover, - patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, - patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + patch("agentic_fabric.core.manager.get_fabric_agent_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_fabric_agent_auto") as mock_run, ): mock_discover.return_value = mock_packages mock_get_config.return_value = mock_config @@ -386,7 +386,7 @@ async def execute_workflow(self, task: str, **kwargs): ) manager = ParallelManager( - crews={"design": "design", "assets": "assets", "qa": "qa"}, + fabric_agents={"design": "design", "assets": "assets", "qa": "qa"}, package_name="test_pkg", ) @@ -395,8 +395,8 @@ async def execute_workflow(self, task: str, **kwargs): with ( patch("agentic_fabric.core.manager.discover_packages") as mock_discover, - patch("agentic_fabric.core.manager.get_crew_config") as mock_get_config, - patch("agentic_fabric.core.manager.run_crew_auto") as mock_run, + patch("agentic_fabric.core.manager.get_fabric_agent_config") as mock_get_config, + patch("agentic_fabric.core.manager.run_fabric_agent_auto") as mock_run, ): mock_discover.return_value = mock_packages mock_get_config.return_value = mock_config diff --git a/packages/agentic-fabric/tests/test_mcp_adapters.py b/packages/agentic-fabric/tests/test_mcp_adapters.py new file mode 100644 index 0000000..9761808 --- /dev/null +++ b/packages/agentic-fabric/tests/test_mcp_adapters.py @@ -0,0 +1,464 @@ +"""Tests for MCP transport adapters.""" + +from __future__ import annotations + +import asyncio +import builtins +import sys +import types + +from dataclasses import dataclass +from typing import Any + +import pytest + +from agentic_fabric.tools import meshy_mcp, vendor_mcp + + +@dataclass +class FakeTextContent: + type: str + text: str + + +class FakeTool: + def __init__(self, name: str, description: str, inputSchema: dict[str, Any]) -> None: + self.name = name + self.description = description + self.inputSchema = inputSchema + + +class FakeServer: + """Minimal MCP server stand-in that records registered handlers.""" + + def __init__(self, name: str) -> None: + self.name = name + self.list_tools_handler: Any | None = None + self.call_tool_handler: Any | None = None + + def list_tools(self) -> Any: + def decorator(func: Any) -> Any: + self.list_tools_handler = func + return func + + return decorator + + def call_tool(self) -> Any: + def decorator(func: Any) -> Any: + self.call_tool_handler = func + return func + + return decorator + + +def install_fake_mcp(monkeypatch: pytest.MonkeyPatch) -> None: + """Install fake MCP modules for adapter tests.""" + mcp_module = types.ModuleType("mcp") + server_module = types.ModuleType("mcp.server") + types_module = types.ModuleType("mcp.types") + server_module.Server = FakeServer + types_module.Tool = FakeTool + types_module.TextContent = FakeTextContent + + monkeypatch.setitem(sys.modules, "mcp", mcp_module) + monkeypatch.setitem(sys.modules, "mcp.server", server_module) + monkeypatch.setitem(sys.modules, "mcp.types", types_module) + + +def install_fake_stdio(monkeypatch: pytest.MonkeyPatch) -> list[tuple[Any, Any, Any]]: + """Install a fake MCP stdio server and return recorded run calls.""" + calls: list[tuple[Any, Any, Any]] = [] + + class FakeStdio: + async def __aenter__(self) -> tuple[str, str]: + return ("read", "write") + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: types.TracebackType | None, + ) -> bool: + return False + + stdio_module = types.ModuleType("mcp.server.stdio") + stdio_module.stdio_server = FakeStdio + monkeypatch.setitem(sys.modules, "mcp.server.stdio", stdio_module) + return calls + + +class RunnableServer: + def __init__(self, calls: list[tuple[Any, Any, Any]]) -> None: + self.calls = calls + + def create_initialization_options(self) -> dict[str, bool]: + return {"ready": True} + + async def run(self, read_stream: Any, write_stream: Any, options: Any) -> None: + self.calls.append((read_stream, write_stream, options)) + + +def install_fake_extended_data(monkeypatch: pytest.MonkeyPatch) -> None: + """Install fake extended-data helpers to cover optional success branches.""" + extended_data_module = types.ModuleType("extended_data") + containers_module = types.ModuleType("extended_data.containers") + redaction_module = types.ModuleType("extended_data.primitives.redaction") + io_module = types.ModuleType("extended_data.io") + + containers_module.to_builtin = lambda value: {"converted": value} + redaction_module.redact_sensitive_data = lambda value: {"redacted": value} + redaction_module.redact_sensitive_text = lambda value, *, values=None: f"redacted:{value}" + io_module.wrap_raw_data_for_export = lambda payload, **kwargs: f"wrapped:{payload}:{sorted(kwargs)}" + + monkeypatch.setitem(sys.modules, "extended_data", extended_data_module) + monkeypatch.setitem(sys.modules, "extended_data.containers", containers_module) + monkeypatch.setitem(sys.modules, "extended_data.primitives.redaction", redaction_module) + monkeypatch.setitem(sys.modules, "extended_data.io", io_module) + + +def reject_imports(monkeypatch: pytest.MonkeyPatch, prefix: str) -> None: + """Reject imports under a package prefix.""" + real_import = builtins.__import__ + + def fake_import(name: str, *args: Any, **kwargs: Any) -> Any: + if name == prefix or name.startswith(f"{prefix}."): + raise ImportError(f"missing {prefix}") + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", fake_import) + + +def test_meshy_mcp_reports_missing_mcp(monkeypatch: pytest.MonkeyPatch) -> None: + reject_imports(monkeypatch, "mcp") + + with pytest.raises(ImportError, match=r"agentic-fabric\[mcp\]"): + meshy_mcp.create_server() + + +def test_meshy_mcp_create_tools_reports_missing_mcp_types(monkeypatch: pytest.MonkeyPatch) -> None: + reject_imports(monkeypatch, "mcp.types") + + with pytest.raises(ImportError, match=r"agentic-fabric\[mcp\]"): + meshy_mcp._create_mcp_tools() + + +def test_meshy_mcp_reports_missing_vendor_fabric(monkeypatch: pytest.MonkeyPatch) -> None: + install_fake_mcp(monkeypatch) + reject_imports(monkeypatch, "vendor_fabric") + + with pytest.raises(ImportError, match=r"vendor-fabric\[meshy\]"): + meshy_mcp.create_server() + + +def test_meshy_mcp_exposes_vendor_tool_definitions(monkeypatch: pytest.MonkeyPatch) -> None: + install_fake_mcp(monkeypatch) + + class FakeSchema: + @staticmethod + def model_json_schema() -> dict[str, Any]: + return {"properties": {"prompt": {"type": "string"}}, "required": ["prompt"]} + + def generate(prompt: str) -> dict[str, str]: + return {"ok": prompt} + + vendor_module = types.ModuleType("vendor_fabric") + meshy_module = types.ModuleType("vendor_fabric.meshy") + tools_module = types.ModuleType("vendor_fabric.meshy.tools") + tools_module.TOOL_DEFINITIONS = [ + { + "name": "text3d_generate", + "description": "Generate a model.", + "schema": FakeSchema, + "func": generate, + } + ] + monkeypatch.setitem(sys.modules, "vendor_fabric", vendor_module) + monkeypatch.setitem(sys.modules, "vendor_fabric.meshy", meshy_module) + monkeypatch.setitem(sys.modules, "vendor_fabric.meshy.tools", tools_module) + + server = meshy_mcp.create_server() + listed = asyncio.run(server.list_tools_handler()) + result = asyncio.run(server.call_tool_handler("text3d_generate", {"prompt": "cube"})) + unknown = asyncio.run(server.call_tool_handler("missing", None)) + + assert server.name == "meshy-ai" + assert [(tool.name, tool.description, tool.inputSchema) for tool in listed] == [ + ( + "text3d_generate", + "Generate a model.", + {"type": "object", "properties": {"prompt": {"type": "string"}}, "required": ["prompt"]}, + ) + ] + assert "cube" in result[0].text + assert "Unknown tool: missing" in unknown[0].text + + +def test_meshy_mcp_handles_tool_errors(monkeypatch: pytest.MonkeyPatch) -> None: + install_fake_mcp(monkeypatch) + + def explode(prompt: str) -> dict[str, str]: + raise RuntimeError(f"failed {prompt}") + + tools_module = types.ModuleType("vendor_fabric.meshy.tools") + tools_module.TOOL_DEFINITIONS = [{"name": "explode", "schema": None, "func": explode}] + monkeypatch.setitem(sys.modules, "vendor_fabric", types.ModuleType("vendor_fabric")) + monkeypatch.setitem(sys.modules, "vendor_fabric.meshy", types.ModuleType("vendor_fabric.meshy")) + monkeypatch.setitem(sys.modules, "vendor_fabric.meshy.tools", tools_module) + + server = meshy_mcp.create_server() + result = asyncio.run(server.call_tool_handler("explode", {"prompt": "cube"})) + + assert "failed cube" in result[0].text + + +def test_meshy_mcp_helper_fallbacks(monkeypatch: pytest.MonkeyPatch) -> None: + reject_imports(monkeypatch, "extended_data") + + class Dumpable: + def model_dump(self) -> dict[str, bool]: + return {"dumped": True} + + assert meshy_mcp._schema_for_definition({}) == {"type": "object", "properties": {}, "required": []} + assert meshy_mcp._schema_for_definition({"schema": object()}) == { + "type": "object", + "properties": {}, + "required": [], + } + assert meshy_mcp._to_builtin(Dumpable()) == {"dumped": True} + assert meshy_mcp._to_builtin([Dumpable()]) == [{"dumped": True}] + assert meshy_mcp._jsonable_tool_result(Dumpable()) == {"dumped": True} + assert meshy_mcp._jsonable_tool_result([Dumpable()]) == [{"dumped": True}] + assert sorted(meshy_mcp._jsonable_tool_result({2, 1})) == [1, 2] + + monkeypatch.setattr(meshy_mcp, "_to_builtin", lambda value: {2, 1} if value == "converted-set" else value) + assert sorted(meshy_mcp._jsonable_tool_result("converted-set")) == [1, 2] + + +def test_meshy_mcp_uses_extended_data_helpers(monkeypatch: pytest.MonkeyPatch) -> None: + install_fake_extended_data(monkeypatch) + + assert meshy_mcp._to_builtin({"value": 1}) == {"converted": {"value": 1}} + assert meshy_mcp._redact_sensitive_data({"token": "secret"}) == {"redacted": {"token": "secret"}} + assert meshy_mcp._redact_sensitive_text("secret") == "redacted:secret" + assert meshy_mcp._tool_payload_text({"ok": True}).startswith("wrapped:") + + +def test_meshy_mcp_run_server_and_main(monkeypatch: pytest.MonkeyPatch) -> None: + install_fake_mcp(monkeypatch) + calls = install_fake_stdio(monkeypatch) + server = RunnableServer(calls) + + meshy_mcp.run_server(server) + + assert calls == [("read", "write", {"ready": True})] + + calls.clear() + monkeypatch.setattr(meshy_mcp, "create_server", lambda: RunnableServer(calls)) + + meshy_mcp.run_server() + + assert calls == [("read", "write", {"ready": True})] + + called: list[bool] = [] + monkeypatch.setattr(meshy_mcp, "run_server", lambda: called.append(True)) + + meshy_mcp.main() + + assert called == [True] + + +def test_meshy_mcp_run_server_reports_missing_stdio(monkeypatch: pytest.MonkeyPatch) -> None: + reject_imports(monkeypatch, "mcp.server.stdio") + + with pytest.raises(ImportError, match=r"agentic-fabric\[mcp\]"): + meshy_mcp.run_server(RunnableServer([])) + + +def install_fake_vendor_fabric(monkeypatch: pytest.MonkeyPatch) -> None: + """Install a fake vendor-fabric registry and data surface.""" + + class DemoConnector: + def search(self, query: str, limit: int = 5) -> dict[str, Any]: + """Search demo records. + + query: Search text. + limit: Maximum results. + """ + return {"query": query, "limit": limit} + + def status(self) -> dict[str, str]: + return {"status": "ok"} + + async def async_lookup(self) -> dict[str, bool]: + return {"async": True} + + def fail(self) -> dict[str, bool]: + raise RuntimeError("connector failed") + + async def list_available_connectors() -> list[str]: + return ["demo"] + + def list_connector_categories(*, include_unavailable: bool = True) -> list[str]: + raise RuntimeError("category failed") + + registry = types.SimpleNamespace( + _list_connector_classes=lambda: {"demo": DemoConnector}, + get_connector=lambda name: DemoConnector(), + list_connectors=lambda *, include_unavailable=True: ["demo"], + list_available_connectors=list_available_connectors, + list_connector_info=lambda *, include_unavailable=True: [{"name": "demo", "available": True}], + get_connector_info=lambda name, *, include_unavailable=True: {"name": name, "available": True}, + list_connector_categories=list_connector_categories, + list_connector_capabilities=lambda *, include_unavailable=True: ["search"], + list_connectors_by_category=lambda category, *, include_unavailable=True: [{"name": "demo", "category": category}], + list_connectors_by_capability=lambda capability, *, include_unavailable=True: [ + {"name": "demo", "capability": capability} + ], + ) + + vendor_module = types.ModuleType("vendor_fabric") + vendor_module.registry = registry + surface_module = types.ModuleType("vendor_fabric.surface") + surface_module.connector_data_methods = lambda connector_class: [ + ("close", connector_class.search), + ("search", connector_class.search), + ("status", connector_class.status), + ("async_lookup", connector_class.async_lookup), + ("fail", connector_class.fail), + ] + + monkeypatch.setitem(sys.modules, "vendor_fabric", vendor_module) + monkeypatch.setitem(sys.modules, "vendor_fabric.surface", surface_module) + + +def test_vendor_mcp_reports_missing_mcp(monkeypatch: pytest.MonkeyPatch) -> None: + reject_imports(monkeypatch, "mcp") + + with pytest.raises(ImportError, match=r"agentic-fabric\[mcp\]"): + vendor_mcp.create_server() + + +def test_vendor_mcp_reports_missing_vendor_fabric(monkeypatch: pytest.MonkeyPatch) -> None: + install_fake_mcp(monkeypatch) + reject_imports(monkeypatch, "vendor_fabric") + + with pytest.raises(ImportError, match="vendor-fabric is required"): + vendor_mcp.create_server() + + +def test_vendor_mcp_exposes_catalog_and_connector_tools(monkeypatch: pytest.MonkeyPatch) -> None: + install_fake_mcp(monkeypatch) + install_fake_vendor_fabric(monkeypatch) + + server = vendor_mcp.create_server() + listed = asyncio.run(server.list_tools_handler()) + names = {tool.name for tool in listed} + catalog_result = asyncio.run(server.call_tool_handler("fabric_vendors_list", None)) + available_result = asyncio.run(server.call_tool_handler("fabric_vendors_list_available", {})) + category_error = asyncio.run(server.call_tool_handler("fabric_vendors_list_categories", {})) + connector_result = asyncio.run(server.call_tool_handler("demo_search", {"query": "alpha", "limit": 2})) + status_result = asyncio.run(server.call_tool_handler("demo_status", {})) + async_result = asyncio.run(server.call_tool_handler("demo_async_lookup", {})) + connector_error = asyncio.run(server.call_tool_handler("demo_fail", {})) + unknown = asyncio.run(server.call_tool_handler("missing", {})) + + assert server.name == "vendor-fabric" + assert "fabric_vendors_list" in names + assert "demo_search" in names + assert "demo" in catalog_result[0].text + assert "demo" in available_result[0].text + assert "category failed" in category_error[0].text + assert "alpha" in connector_result[0].text + assert "ok" in status_result[0].text + assert "async" in async_result[0].text + assert "connector failed" in connector_error[0].text + assert "Unknown tool: missing" in unknown[0].text + + +def test_vendor_mcp_helper_branches(monkeypatch: pytest.MonkeyPatch) -> None: + reject_imports(monkeypatch, "extended_data") + + class Dumpable: + def model_dump(self) -> dict[str, bool]: + return {"dumped": True} + + def typed( + self: object, + count: int, + ratio: float, + enabled: bool, + items: list[str], + payload: dict[str, Any], + optional: str = "value", + ) -> dict[str, Any]: + """Typed method. + + count: Count value. + """ + return {} + + def unresolved(value: MissingType) -> dict[str, Any]: # noqa: F821 + return {} + + schema = vendor_mcp._get_method_schema(typed) + unresolved_schema = vendor_mcp._get_method_schema(unresolved) + + assert schema["properties"]["count"]["type"] == "integer" + assert schema["properties"]["ratio"]["type"] == "number" + assert schema["properties"]["enabled"]["type"] == "boolean" + assert schema["properties"]["items"]["type"] == "array" + assert schema["properties"]["payload"]["type"] == "object" + assert schema["properties"]["optional"]["default"] == "value" + assert schema["properties"]["count"]["description"] == "Count value." + assert unresolved_schema["properties"]["value"]["type"] == "string" + assert vendor_mcp._connector_classes(types.SimpleNamespace()) == {} + assert vendor_mcp._to_builtin(Dumpable()) == {"dumped": True} + assert vendor_mcp._to_builtin([Dumpable()]) == [{"dumped": True}] + assert vendor_mcp._jsonable_tool_result(Dumpable()) == {"dumped": True} + assert vendor_mcp._jsonable_tool_result([Dumpable()]) == [{"dumped": True}] + assert sorted(vendor_mcp._jsonable_tool_result({2, 1})) == [1, 2] + + monkeypatch.setattr(vendor_mcp, "_to_builtin", lambda value: {2, 1} if value == "converted-set" else value) + assert sorted(vendor_mcp._jsonable_tool_result("converted-set")) == [1, 2] + + +def test_vendor_mcp_uses_extended_data_helpers(monkeypatch: pytest.MonkeyPatch) -> None: + install_fake_extended_data(monkeypatch) + + assert vendor_mcp._to_builtin({"value": 1}) == {"converted": {"value": 1}} + assert vendor_mcp._redact_sensitive_data({"token": "secret"}) == {"redacted": {"token": "secret"}} + assert vendor_mcp._tool_error_text(RuntimeError("secret")) == "Error: RuntimeError: redacted:secret" + assert vendor_mcp._unknown_tool_text("secret") == "Unknown tool: redacted:secret" + assert vendor_mcp._tool_result_text({"ok": True}).startswith("wrapped:") + + +def test_vendor_mcp_run_server_and_main(monkeypatch: pytest.MonkeyPatch) -> None: + install_fake_mcp(monkeypatch) + install_fake_vendor_fabric(monkeypatch) + calls = install_fake_stdio(monkeypatch) + server = RunnableServer(calls) + + vendor_mcp.run_server(server) + + assert calls == [("read", "write", {"ready": True})] + + calls.clear() + monkeypatch.setattr(vendor_mcp, "create_server", lambda: RunnableServer(calls)) + + vendor_mcp.run_server() + + assert calls == [("read", "write", {"ready": True})] + + called: list[bool] = [] + monkeypatch.setattr(vendor_mcp, "run_server", lambda: called.append(True)) + + vendor_mcp.main() + + assert called == [True] + + +def test_vendor_mcp_run_server_reports_missing_stdio(monkeypatch: pytest.MonkeyPatch) -> None: + reject_imports(monkeypatch, "mcp.server.stdio") + + with pytest.raises(ImportError, match=r"agentic-fabric\[mcp\]"): + vendor_mcp.run_server(RunnableServer([])) diff --git a/packages/agentic-fabric/tests/test_runners.py b/packages/agentic-fabric/tests/test_runners.py index e65ea0b..17aad4c 100644 --- a/packages/agentic-fabric/tests/test_runners.py +++ b/packages/agentic-fabric/tests/test_runners.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: - from tests._crew_mocker import CrewMocker + from tests._fabric_mocker import FabricMocker def reject_imports(*blocked_names: str): @@ -31,11 +31,11 @@ def fake_import(name: str, *args: Any, **kwargs: Any) -> Any: class TestCrewAIRunner: """Tests for CrewAI runner implementation.""" - def test_init_reports_missing_crewai(self, crew_mocker: CrewMocker) -> None: + def test_init_reports_missing_crewai(self, fabric_mocker: FabricMocker) -> None: """CrewAI runner construction should explain how to install CrewAI.""" from agentic_fabric.runners.crewai_runner import CrewAIRunner - crew_mocker.patch("builtins.__import__", side_effect=reject_imports("crewai")) + fabric_mocker.patch("builtins.__import__", side_effect=reject_imports("crewai")) try: CrewAIRunner() @@ -44,14 +44,14 @@ def test_init_reports_missing_crewai(self, crew_mocker: CrewMocker) -> None: else: # pragma: no cover - defensive if CrewAI import mocking stops working raise AssertionError("CrewAIRunner did not report missing CrewAI") - def test_build_agent_creates_crewai_agent(self, crew_mocker: CrewMocker) -> None: + def test_build_agent_creates_crewai_agent(self, fabric_mocker: FabricMocker) -> None: """Test that build_agent creates a CrewAI Agent.""" - crew_mocker.mock_crewai() + fabric_mocker.mock_crewai() from agentic_fabric.runners.crewai_runner import CrewAIRunner - MockAgent = crew_mocker.patch_crewai_agent() - crew_mocker.patch_get_llm() + MockAgent = fabric_mocker.patch_crewai_agent() + fabric_mocker.patch_get_llm() runner = CrewAIRunner() @@ -62,7 +62,7 @@ def test_build_agent_creates_crewai_agent(self, crew_mocker: CrewMocker) -> None "allow_delegation": True, } - tools = [crew_mocker.MagicMock()] + tools = [fabric_mocker.MagicMock()] runner.build_agent(agent_config, tools=tools) MockAgent.assert_called_once() @@ -73,13 +73,13 @@ def test_build_agent_creates_crewai_agent(self, crew_mocker: CrewMocker) -> None assert call_kwargs["allow_delegation"] is True assert call_kwargs["tools"] == tools - def test_build_task_creates_crewai_task(self, crew_mocker: CrewMocker) -> None: + def test_build_task_creates_crewai_task(self, fabric_mocker: FabricMocker) -> None: """Test that build_task creates a CrewAI Task.""" - crew_mocker.mock_crewai() + fabric_mocker.mock_crewai() from agentic_fabric.runners.crewai_runner import CrewAIRunner - MockTask = crew_mocker.patch_crewai_task() + MockTask = fabric_mocker.patch_crewai_task() runner = CrewAIRunner() @@ -87,7 +87,7 @@ def test_build_task_creates_crewai_task(self, crew_mocker: CrewMocker) -> None: "description": "Test task description", "expected_output": "Test output", } - mock_agent = crew_mocker.MagicMock() + mock_agent = fabric_mocker.MagicMock() runner.build_task(task_config, mock_agent) @@ -97,13 +97,13 @@ def test_build_task_creates_crewai_task(self, crew_mocker: CrewMocker) -> None: assert call_kwargs["expected_output"] == "Test output" assert call_kwargs["agent"] == mock_agent - def test_build_task_includes_context(self, crew_mocker: CrewMocker) -> None: + def test_build_task_includes_context(self, fabric_mocker: FabricMocker) -> None: """Test that build_task includes context tasks when provided.""" - crew_mocker.mock_crewai() + fabric_mocker.mock_crewai() from agentic_fabric.runners.crewai_runner import CrewAIRunner - MockTask = crew_mocker.patch_crewai_task() + MockTask = fabric_mocker.patch_crewai_task() runner = CrewAIRunner() @@ -111,36 +111,36 @@ def test_build_task_includes_context(self, crew_mocker: CrewMocker) -> None: "description": "Test task", "expected_output": "Test output", } - mock_agent = crew_mocker.MagicMock() - context_tasks = [crew_mocker.MagicMock(), crew_mocker.MagicMock()] + mock_agent = fabric_mocker.MagicMock() + context_tasks = [fabric_mocker.MagicMock(), fabric_mocker.MagicMock()] runner.build_task(task_config, mock_agent, context=context_tasks) call_kwargs = MockTask.call_args[1] assert call_kwargs["context"] == context_tasks - def test_build_crew_creates_crewai_crew(self, crew_mocker: CrewMocker) -> None: - """Test that build_crew creates a CrewAI Crew.""" - crew_mocker.mock_crewai() + def test_build_fabric_agent_creates_crewai_crew(self, fabric_mocker: FabricMocker) -> None: + """Test that build_fabric_agent creates a CrewAI Crew.""" + fabric_mocker.mock_crewai() from agentic_fabric.runners.crewai_runner import CrewAIRunner - MockCrew = crew_mocker.patch_crewai_crew() - MockAgent = crew_mocker.patch_crewai_agent() - MockTask = crew_mocker.patch_crewai_task() - crew_mocker.patch_crewai_process() - crew_mocker.patch_get_llm() + MockCrew = fabric_mocker.patch_crewai_crew() + MockAgent = fabric_mocker.patch_crewai_agent() + MockTask = fabric_mocker.patch_crewai_task() + fabric_mocker.patch_crewai_process() + fabric_mocker.patch_get_llm() runner = CrewAIRunner() - mock_agent = crew_mocker.MagicMock() + mock_agent = fabric_mocker.MagicMock() MockAgent.return_value = mock_agent - mock_task = crew_mocker.MagicMock() + mock_task = fabric_mocker.MagicMock() MockTask.return_value = mock_task - crew_config = { - "name": "test_crew", + fabric_agent_config = { + "name": "test_fabric_agent", "agents": { "agent1": { "role": "Agent 1", @@ -158,7 +158,7 @@ def test_build_crew_creates_crewai_crew(self, crew_mocker: CrewMocker) -> None: "knowledge_paths": [], } - runner.build_crew(crew_config) + runner.build_fabric_agent(fabric_agent_config) MockCrew.assert_called_once() call_kwargs = MockCrew.call_args[1] @@ -167,43 +167,43 @@ def test_build_crew_creates_crewai_crew(self, crew_mocker: CrewMocker) -> None: assert call_kwargs["planning"] is True assert call_kwargs["memory"] is True - def test_run_executes_crew_and_returns_result(self, crew_mocker: CrewMocker) -> None: - """Test that run executes crew and returns string result.""" - crew_mocker.mock_crewai() + def test_run_executes_crewai_crew_and_returns_result(self, fabric_mocker: FabricMocker) -> None: + """Test that run executes fabric_agent and returns string result.""" + fabric_mocker.mock_crewai() from agentic_fabric.runners.crewai_runner import CrewAIRunner runner = CrewAIRunner() - mock_crew = crew_mocker.mock_crewai_crew(result="Test output") + mock_crew = fabric_mocker.mock_crewai_crew(result="Test output") result = runner.run(mock_crew, {"input": "test input"}) mock_crew.kickoff.assert_called_once_with(inputs={"input": "test input"}) assert result == "Test output" - def test_run_handles_result_without_raw(self, crew_mocker: CrewMocker) -> None: + def test_run_handles_result_without_raw(self, fabric_mocker: FabricMocker) -> None: """Test that run handles results without raw attribute.""" - crew_mocker.mock_crewai() + fabric_mocker.mock_crewai() from agentic_fabric.runners.crewai_runner import CrewAIRunner runner = CrewAIRunner() - mock_crew = crew_mocker.MagicMock() + mock_crew = fabric_mocker.MagicMock() mock_crew.kickoff.return_value = "Direct string result" result = runner.run(mock_crew, {}) assert result == "Direct string result" - def test_handles_knowledge_sources(self, crew_mocker: CrewMocker, tmp_path) -> None: + def test_handles_knowledge_sources(self, fabric_mocker: FabricMocker, tmp_path) -> None: """Test that knowledge sources are loaded from paths.""" - crew_mocker.mock_crewai() + fabric_mocker.mock_crewai() from agentic_fabric.runners.crewai_runner import CrewAIRunner - MockKnowledge = crew_mocker.patch_knowledge_source() + MockKnowledge = fabric_mocker.patch_knowledge_source() runner = CrewAIRunner() @@ -218,28 +218,28 @@ def test_handles_knowledge_sources(self, crew_mocker: CrewMocker, tmp_path) -> N assert MockKnowledge.called assert len(sources) > 0 - def test_handles_task_context_dependencies(self, crew_mocker: CrewMocker) -> None: + def test_handles_task_context_dependencies(self, fabric_mocker: FabricMocker) -> None: """Test that tasks with context dependencies are handled correctly.""" - crew_mocker.mock_crewai() + fabric_mocker.mock_crewai() from agentic_fabric.runners.crewai_runner import CrewAIRunner - crew_mocker.patch_crewai_crew() - MockAgent = crew_mocker.patch_crewai_agent() - MockTask = crew_mocker.patch_crewai_task() - crew_mocker.patch_crewai_process() - crew_mocker.patch_get_llm() + fabric_mocker.patch_crewai_crew() + MockAgent = fabric_mocker.patch_crewai_agent() + MockTask = fabric_mocker.patch_crewai_task() + fabric_mocker.patch_crewai_process() + fabric_mocker.patch_get_llm() runner = CrewAIRunner() - mock_agent = crew_mocker.MagicMock() + mock_agent = fabric_mocker.MagicMock() MockAgent.return_value = mock_agent - mock_task1 = crew_mocker.MagicMock() - mock_task2 = crew_mocker.MagicMock() + mock_task1 = fabric_mocker.MagicMock() + mock_task2 = fabric_mocker.MagicMock() MockTask.side_effect = [mock_task1, mock_task2] - crew_config = { + fabric_agent_config = { "agents": { "agent1": { "role": "Agent", @@ -263,7 +263,7 @@ def test_handles_task_context_dependencies(self, crew_mocker: CrewMocker) -> Non "knowledge_paths": [], } - runner.build_crew(crew_config) + runner.build_fabric_agent(fabric_agent_config) # Verify second task was created with context assert MockTask.call_count == 2 @@ -271,24 +271,24 @@ def test_handles_task_context_dependencies(self, crew_mocker: CrewMocker) -> Non assert "context" in second_task_kwargs assert second_task_kwargs["context"] == [mock_task1] - def test_build_crew_warns_for_unresolved_context_dependencies( + def test_build_fabric_agent_warns_for_unresolved_context_dependencies( self, - crew_mocker: CrewMocker, + fabric_mocker: FabricMocker, caplog, ) -> None: """Unresolved task context references should be visible to callers.""" - crew_mocker.mock_crewai() + fabric_mocker.mock_crewai() from agentic_fabric.runners.crewai_runner import CrewAIRunner - crew_mocker.patch_crewai_crew() - crew_mocker.patch_crewai_agent() - crew_mocker.patch_crewai_task() - crew_mocker.patch_crewai_process() - crew_mocker.patch_get_llm() + fabric_mocker.patch_crewai_crew() + fabric_mocker.patch_crewai_agent() + fabric_mocker.patch_crewai_task() + fabric_mocker.patch_crewai_process() + fabric_mocker.patch_get_llm() runner = CrewAIRunner() - crew_config = { + fabric_agent_config = { "agents": {"agent1": {"role": "Agent", "goal": "Goal", "backstory": "Story"}}, "tasks": { "task1": { @@ -301,28 +301,28 @@ def test_build_crew_warns_for_unresolved_context_dependencies( "knowledge_paths": [], } - runner.build_crew(crew_config) + runner.build_fabric_agent(fabric_agent_config) assert "references context tasks that are not yet available: later_task" in caplog.text - def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> None: + def test_build_fabric_agent_resolves_declared_tools(self, fabric_mocker: FabricMocker) -> None: """Tool names declared in agent config should be instantiated and attached.""" - crew_mocker.mock_crewai() + fabric_mocker.mock_crewai() from agentic_fabric.runners.crewai_runner import CrewAIRunner - crew_mocker.patch_crewai_crew() - MockAgent = crew_mocker.patch_crewai_agent() - crew_mocker.patch_crewai_task() - crew_mocker.patch_crewai_process() - crew_mocker.patch_get_llm() + fabric_mocker.patch_crewai_crew() + MockAgent = fabric_mocker.patch_crewai_agent() + fabric_mocker.patch_crewai_task() + fabric_mocker.patch_crewai_process() + fabric_mocker.patch_get_llm() - resolved_tool = crew_mocker.MagicMock() + resolved_tool = fabric_mocker.MagicMock() runner = CrewAIRunner() - runner._resolve_tools = crew_mocker.MagicMock(return_value=[resolved_tool]) + runner._resolve_tools = fabric_mocker.MagicMock(return_value=[resolved_tool]) - crew_config = { + fabric_agent_config = { "agents": { "agent1": { "role": "Agent 1", @@ -341,26 +341,26 @@ def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> No "knowledge_paths": [], } - runner.build_crew(crew_config) + runner.build_fabric_agent(fabric_agent_config) runner._resolve_tools.assert_called_once_with(["FileWriteTool"]) assert MockAgent.call_args[1]["tools"] == [resolved_tool] - def test_build_crew_rejects_tasks_with_unknown_agents(self, crew_mocker: CrewMocker) -> None: - """CrewAI tasks must reference agents declared in the crew config.""" - crew_mocker.mock_crewai() + def test_build_fabric_agent_rejects_tasks_with_unknown_agents(self, fabric_mocker: FabricMocker) -> None: + """CrewAI tasks must reference agents declared in the fabric_agent config.""" + fabric_mocker.mock_crewai() from agentic_fabric.runners.crewai_runner import CrewAIRunner runner = CrewAIRunner() - crew_config = { + fabric_agent_config = { "agents": {}, "tasks": {"task1": {"description": "Task", "agent": "missing"}}, } try: - runner.build_crew(crew_config) + runner.build_fabric_agent(fabric_agent_config) except ValueError as exc: assert "invalid agent: missing" in str(exc) else: # pragma: no cover - defensive @@ -368,15 +368,15 @@ def test_build_crew_rejects_tasks_with_unknown_agents(self, crew_mocker: CrewMoc def test_load_knowledge_skips_non_directories_and_logs_read_errors( self, - crew_mocker: CrewMocker, + fabric_mocker: FabricMocker, tmp_path, ) -> None: """Knowledge loading should skip bad paths and tolerate unreadable files.""" - crew_mocker.mock_crewai() + fabric_mocker.mock_crewai() from agentic_fabric.runners.crewai_runner import CrewAIRunner - crew_mocker.patch_knowledge_source() + fabric_mocker.patch_knowledge_source() runner = CrewAIRunner() knowledge_dir = tmp_path / "knowledge" knowledge_dir.mkdir() @@ -389,7 +389,7 @@ def fake_read_text(self, *args: Any, **kwargs: Any) -> str: raise OSError("cannot read") return original_read_text(self, *args, **kwargs) - crew_mocker.patch.object(type(bad_file), "read_text", fake_read_text) + fabric_mocker.patch.object(type(bad_file), "read_text", fake_read_text) assert runner._load_knowledge([tmp_path / "missing", knowledge_dir]) == [] @@ -397,11 +397,11 @@ def fake_read_text(self, *args: Any, **kwargs: Any) -> str: class TestLangGraphRunner: """Tests for LangGraph runner implementation.""" - def test_init_reports_missing_langgraph(self, crew_mocker: CrewMocker) -> None: + def test_init_reports_missing_langgraph(self, fabric_mocker: FabricMocker) -> None: """LangGraph runner construction should explain how to install LangGraph.""" from agentic_fabric.runners.langgraph_runner import LangGraphRunner - crew_mocker.patch("builtins.__import__", side_effect=reject_imports("langgraph")) + fabric_mocker.patch("builtins.__import__", side_effect=reject_imports("langgraph")) try: LangGraphRunner() @@ -410,15 +410,15 @@ def test_init_reports_missing_langgraph(self, crew_mocker: CrewMocker) -> None: else: # pragma: no cover - defensive if LangGraph import mocking stops working raise AssertionError("LangGraphRunner did not report missing LangGraph") - def test_build_agent_creates_react_agent(self, crew_mocker: CrewMocker) -> None: + def test_build_agent_creates_react_agent(self, fabric_mocker: FabricMocker) -> None: """Test that build_agent creates a LangGraph ReAct agent.""" - crew_mocker.mock_langgraph() + fabric_mocker.mock_langgraph() from agentic_fabric.runners.langgraph_runner import LangGraphRunner - mock_create = crew_mocker.patch_create_react_agent() - MockLLM = crew_mocker.patch_chat_anthropic() - mock_llm = crew_mocker.MagicMock() + mock_create = fabric_mocker.patch_create_react_agent() + MockLLM = fabric_mocker.patch_chat_anthropic() + mock_llm = fabric_mocker.MagicMock() MockLLM.return_value = mock_llm runner = LangGraphRunner() @@ -428,7 +428,7 @@ def test_build_agent_creates_react_agent(self, crew_mocker: CrewMocker) -> None: "goal": "Test goal", "llm": "claude-sonnet-4-20250514", } - tools = [crew_mocker.MagicMock()] + tools = [fabric_mocker.MagicMock()] runner.build_agent(agent_config, tools=tools) @@ -437,36 +437,36 @@ def test_build_agent_creates_react_agent(self, crew_mocker: CrewMocker) -> None: assert args[0] == mock_llm assert args[1] == tools - def test_build_crew_creates_graph(self, crew_mocker: CrewMocker) -> None: - """Test that build_crew creates a LangGraph workflow.""" - crew_mocker.mock_langgraph() + def test_build_fabric_agent_creates_graph(self, fabric_mocker: FabricMocker) -> None: + """Test that build_fabric_agent creates a LangGraph workflow.""" + fabric_mocker.mock_langgraph() from agentic_fabric.runners.langgraph_runner import LangGraphRunner - mock_create = crew_mocker.patch_create_react_agent() - crew_mocker.patch_chat_anthropic() + mock_create = fabric_mocker.patch_create_react_agent() + fabric_mocker.patch_chat_anthropic() runner = LangGraphRunner() - crew_config = { + fabric_agent_config = { "llm": {"model": "claude-sonnet-4-20250514"}, "agents": {}, "tasks": {}, } - runner.build_crew(crew_config) + runner.build_fabric_agent(fabric_agent_config) mock_create.assert_called_once() - def test_run_invokes_graph(self, crew_mocker: CrewMocker) -> None: + def test_run_invokes_graph(self, fabric_mocker: FabricMocker) -> None: """Test that run invokes the LangGraph workflow.""" - crew_mocker.mock_langgraph() + fabric_mocker.mock_langgraph() from agentic_fabric.runners.langgraph_runner import LangGraphRunner runner = LangGraphRunner() - mock_graph = crew_mocker.mock_langgraph_graph(result="Test response") + mock_graph = fabric_mocker.mock_langgraph_graph(result="Test response") result = runner.run(mock_graph, {"input": "test prompt"}) @@ -475,15 +475,15 @@ def test_run_invokes_graph(self, crew_mocker: CrewMocker) -> None: assert "messages" in invoke_args assert result == "Test response" - def test_run_handles_dict_messages(self, crew_mocker: CrewMocker) -> None: + def test_run_handles_dict_messages(self, fabric_mocker: FabricMocker) -> None: """Test that run handles dictionary message format.""" - crew_mocker.mock_langgraph() + fabric_mocker.mock_langgraph() from agentic_fabric.runners.langgraph_runner import LangGraphRunner runner = LangGraphRunner() - mock_graph = crew_mocker.MagicMock() + mock_graph = fabric_mocker.MagicMock() mock_graph.invoke.return_value = {"messages": [("assistant", "Test response")]} runner.run(mock_graph, {"task": "test task"}) @@ -493,38 +493,38 @@ def test_run_handles_dict_messages(self, crew_mocker: CrewMocker) -> None: assert "messages" in invoke_args assert any("test task" in str(msg) for msg in invoke_args["messages"]) - def test_run_returns_raw_result_when_no_messages(self, crew_mocker: CrewMocker) -> None: + def test_run_returns_raw_result_when_no_messages(self, fabric_mocker: FabricMocker) -> None: """LangGraph runner should stringify results that do not contain messages.""" - crew_mocker.mock_langgraph() + fabric_mocker.mock_langgraph() from agentic_fabric.runners.langgraph_runner import LangGraphRunner runner = LangGraphRunner() - mock_graph = crew_mocker.MagicMock() + mock_graph = fabric_mocker.MagicMock() mock_graph.invoke.return_value = {"state": "done"} assert runner.run(mock_graph, {"other": "value"}) == "{'state': 'done'}" - def test_get_llm_returns_chat_anthropic(self, crew_mocker: CrewMocker) -> None: + def test_get_llm_returns_chat_anthropic(self, fabric_mocker: FabricMocker) -> None: """Test that get_llm returns ChatAnthropic instance.""" - crew_mocker.mock_langgraph() + fabric_mocker.mock_langgraph() from agentic_fabric.runners.langgraph_runner import LangGraphRunner - MockLLM = crew_mocker.patch_chat_anthropic() + MockLLM = fabric_mocker.patch_chat_anthropic() runner = LangGraphRunner() runner.get_llm("claude-sonnet-4-20250514") MockLLM.assert_called_once_with(model="claude-sonnet-4-20250514") - def test_get_llm_uses_default_model(self, crew_mocker: CrewMocker) -> None: + def test_get_llm_uses_default_model(self, fabric_mocker: FabricMocker) -> None: """Test that get_llm uses default model when none specified.""" - crew_mocker.mock_langgraph() + fabric_mocker.mock_langgraph() from agentic_fabric.runners.langgraph_runner import LangGraphRunner - MockLLM = crew_mocker.patch_chat_anthropic() + MockLLM = fabric_mocker.patch_chat_anthropic() runner = LangGraphRunner() runner.get_llm() @@ -532,9 +532,9 @@ def test_get_llm_uses_default_model(self, crew_mocker: CrewMocker) -> None: MockLLM.assert_called_once() assert MockLLM.call_args[1]["model"] == "claude-haiku-4-5-20251001" - def test_build_task_returns_dict(self, crew_mocker: CrewMocker) -> None: + def test_build_task_returns_dict(self, fabric_mocker: FabricMocker) -> None: """Test that build_task returns task configuration dict.""" - crew_mocker.mock_langgraph() + fabric_mocker.mock_langgraph() from agentic_fabric.runners.langgraph_runner import LangGraphRunner @@ -544,7 +544,7 @@ def test_build_task_returns_dict(self, crew_mocker: CrewMocker) -> None: "description": "Test task", "expected_output": "Test output", } - mock_agent = crew_mocker.MagicMock() + mock_agent = fabric_mocker.MagicMock() result = runner.build_task(task_config, mock_agent) @@ -553,14 +553,14 @@ def test_build_task_returns_dict(self, crew_mocker: CrewMocker) -> None: assert result["expected_output"] == "Test output" assert result["agent"] == mock_agent - def test_handles_multi_agent_flows(self, crew_mocker: CrewMocker) -> None: + def test_handles_multi_agent_flows(self, fabric_mocker: FabricMocker) -> None: """Test that multiple agents can be created for complex flows.""" - crew_mocker.mock_langgraph() + fabric_mocker.mock_langgraph() from agentic_fabric.runners.langgraph_runner import LangGraphRunner - mock_create = crew_mocker.patch_create_react_agent() - crew_mocker.patch_chat_anthropic() + mock_create = fabric_mocker.patch_create_react_agent() + fabric_mocker.patch_chat_anthropic() runner = LangGraphRunner() @@ -574,19 +574,19 @@ def test_handles_multi_agent_flows(self, crew_mocker: CrewMocker) -> None: # Verify create_react_agent was called twice assert mock_create.call_count == 2 - def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> None: - """Configured agent tools should be adapted for LangGraph crews.""" - crew_mocker.mock_langgraph() + def test_build_fabric_agent_resolves_declared_tools(self, fabric_mocker: FabricMocker) -> None: + """Configured agent tools should be adapted for LangGraph fabric_agents.""" + fabric_mocker.mock_langgraph() from agentic_fabric.runners.langgraph_runner import LangGraphRunner - mock_create = crew_mocker.patch_create_react_agent() - crew_mocker.patch_chat_anthropic() - resolved_tool = crew_mocker.MagicMock() - crew_mocker.patch("agentic_fabric.runners.langgraph_runner.resolve_langgraph_tools", return_value=[resolved_tool]) + mock_create = fabric_mocker.patch_create_react_agent() + fabric_mocker.patch_chat_anthropic() + resolved_tool = fabric_mocker.MagicMock() + fabric_mocker.patch("agentic_fabric.runners.langgraph_runner.resolve_langgraph_tools", return_value=[resolved_tool]) runner = LangGraphRunner() - crew_config = { + fabric_agent_config = { "llm": {"model": "claude-sonnet-4-20250514"}, "agents": { "researcher": { @@ -596,7 +596,7 @@ def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> No "tasks": {}, } - runner.build_crew(crew_config) + runner.build_fabric_agent(fabric_agent_config) mock_create.assert_called_once() assert mock_create.call_args[0][1] == [resolved_tool] @@ -605,11 +605,11 @@ def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> No class TestStrandsRunner: """Tests for Strands runner implementation.""" - def test_init_reports_missing_strands(self, crew_mocker: CrewMocker) -> None: + def test_init_reports_missing_strands(self, fabric_mocker: FabricMocker) -> None: """Strands runner construction should explain how to install Strands.""" from agentic_fabric.runners.strands_runner import StrandsRunner - crew_mocker.patch("builtins.__import__", side_effect=reject_imports("strands")) + fabric_mocker.patch("builtins.__import__", side_effect=reject_imports("strands")) try: StrandsRunner() @@ -618,13 +618,13 @@ def test_init_reports_missing_strands(self, crew_mocker: CrewMocker) -> None: else: # pragma: no cover - defensive if Strands import mocking stops working raise AssertionError("StrandsRunner did not report missing Strands") - def test_build_agent_creates_strands_agent(self, crew_mocker: CrewMocker) -> None: + def test_build_agent_creates_strands_agent(self, fabric_mocker: FabricMocker) -> None: """Test that build_agent creates a Strands Agent.""" - crew_mocker.mock_strands() + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner - MockAgent = crew_mocker.patch_strands_agent() + MockAgent = fabric_mocker.patch_strands_agent() runner = StrandsRunner() @@ -633,7 +633,7 @@ def test_build_agent_creates_strands_agent(self, crew_mocker: CrewMocker) -> Non "goal": "Test goal", "backstory": "Test backstory", } - tools = [crew_mocker.MagicMock()] + tools = [fabric_mocker.MagicMock()] runner.build_agent(agent_config, tools=tools) @@ -644,16 +644,16 @@ def test_build_agent_creates_strands_agent(self, crew_mocker: CrewMocker) -> Non assert "Test backstory" in call_kwargs["system_prompt"] assert call_kwargs["tools"] == tools - def test_build_system_prompt_combines_agent_prompts(self, crew_mocker: CrewMocker) -> None: + def test_build_system_prompt_combines_agent_prompts(self, fabric_mocker: FabricMocker) -> None: """Test that _build_system_prompt combines multiple agent roles.""" - crew_mocker.mock_strands() + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner runner = StrandsRunner() - crew_config = { - "description": "Test crew description", + fabric_agent_config = { + "description": "Test fabric_agent description", "agents": { "agent1": { "role": "Agent One", @@ -671,25 +671,25 @@ def test_build_system_prompt_combines_agent_prompts(self, crew_mocker: CrewMocke }, } - prompt = runner._build_system_prompt(crew_config) + prompt = runner._build_system_prompt(fabric_agent_config) - assert "Test crew description" in prompt + assert "Test fabric_agent description" in prompt assert "Agent One" in prompt assert "Agent Two" in prompt assert "Goal one" in prompt assert "Goal two" in prompt assert "task1" in prompt - def test_build_system_prompt_truncates_long_descriptions(self, crew_mocker: CrewMocker) -> None: + def test_build_system_prompt_truncates_long_descriptions(self, fabric_mocker: FabricMocker) -> None: """Test that long task descriptions are truncated.""" - crew_mocker.mock_strands() + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner runner = StrandsRunner() long_desc = "A" * 250 - crew_config = { + fabric_agent_config = { "tasks": { "task1": { "description": long_desc, @@ -697,22 +697,22 @@ def test_build_system_prompt_truncates_long_descriptions(self, crew_mocker: Crew }, } - prompt = runner._build_system_prompt(crew_config) + prompt = runner._build_system_prompt(fabric_agent_config) # Should be truncated to 200 chars + ellipsis assert "..." in prompt assert prompt.count("A") == 200 - def test_build_system_prompt_keeps_short_descriptions(self, crew_mocker: CrewMocker) -> None: + def test_build_system_prompt_keeps_short_descriptions(self, fabric_mocker: FabricMocker) -> None: """Test that short task descriptions are not truncated.""" - crew_mocker.mock_strands() + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner runner = StrandsRunner() short_desc = "Short description" - crew_config = { + fabric_agent_config = { "tasks": { "task1": { "description": short_desc, @@ -720,44 +720,44 @@ def test_build_system_prompt_keeps_short_descriptions(self, crew_mocker: CrewMoc }, } - prompt = runner._build_system_prompt(crew_config) + prompt = runner._build_system_prompt(fabric_agent_config) # Should not have ellipsis for short descriptions assert "..." not in prompt assert short_desc in prompt - def test_run_returns_string_result(self, crew_mocker: CrewMocker) -> None: + def test_run_returns_string_result(self, fabric_mocker: FabricMocker) -> None: """Test that run executes agent and returns string result.""" - crew_mocker.mock_strands() + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner runner = StrandsRunner() - mock_agent = crew_mocker.mock_strands_agent(result="Test agent response") + mock_agent = fabric_mocker.mock_strands_agent(result="Test agent response") result = runner.run(mock_agent, {"input": "test prompt"}) mock_agent.assert_called_once_with("test prompt") assert result == "Test agent response" - def test_run_handles_task_input(self, crew_mocker: CrewMocker) -> None: + def test_run_handles_task_input(self, fabric_mocker: FabricMocker) -> None: """Test that run handles 'task' key in inputs.""" - crew_mocker.mock_strands() + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner runner = StrandsRunner() - mock_agent = crew_mocker.mock_strands_agent(result="Response") + mock_agent = fabric_mocker.mock_strands_agent(result="Response") runner.run(mock_agent, {"task": "do something"}) mock_agent.assert_called_once_with("do something") - def test_get_model_provider_from_string(self, crew_mocker: CrewMocker) -> None: + def test_get_model_provider_from_string(self, fabric_mocker: FabricMocker) -> None: """Test extracting model provider from string config.""" - crew_mocker.mock_strands() + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner @@ -767,9 +767,9 @@ def test_get_model_provider_from_string(self, crew_mocker: CrewMocker) -> None: assert result == "claude-3-5-sonnet" - def test_get_model_provider_from_dict(self, crew_mocker: CrewMocker) -> None: + def test_get_model_provider_from_dict(self, fabric_mocker: FabricMocker) -> None: """Test extracting model provider from dict config.""" - crew_mocker.mock_strands() + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner @@ -779,9 +779,9 @@ def test_get_model_provider_from_dict(self, crew_mocker: CrewMocker) -> None: assert result == "claude-3-5-sonnet" - def test_get_model_provider_returns_none_for_empty(self, crew_mocker: CrewMocker) -> None: + def test_get_model_provider_returns_none_for_empty(self, fabric_mocker: FabricMocker) -> None: """Test that None is returned when no config provided.""" - crew_mocker.mock_strands() + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner @@ -791,33 +791,33 @@ def test_get_model_provider_returns_none_for_empty(self, crew_mocker: CrewMocker assert result is None - def test_build_crew_creates_strands_agent(self, crew_mocker: CrewMocker) -> None: - """Test that build_crew creates a Strands agent.""" - crew_mocker.mock_strands() + def test_build_fabric_agent_creates_strands_agent(self, fabric_mocker: FabricMocker) -> None: + """Test that build_fabric_agent creates a Strands agent.""" + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner - MockAgent = crew_mocker.patch_strands_agent() + MockAgent = fabric_mocker.patch_strands_agent() runner = StrandsRunner() - crew_config = { - "description": "Test crew", + fabric_agent_config = { + "description": "Test fabric_agent", "llm": {"model": "claude-3-5-sonnet"}, "agents": {}, "tasks": {}, } - runner.build_crew(crew_config) + runner.build_fabric_agent(fabric_agent_config) MockAgent.assert_called_once() call_kwargs = MockAgent.call_args[1] assert "system_prompt" in call_kwargs assert call_kwargs["model_id"] == "claude-3-5-sonnet" - def test_build_task_returns_dict(self, crew_mocker: CrewMocker) -> None: + def test_build_task_returns_dict(self, fabric_mocker: FabricMocker) -> None: """Test that build_task returns task configuration dict.""" - crew_mocker.mock_strands() + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner @@ -827,7 +827,7 @@ def test_build_task_returns_dict(self, crew_mocker: CrewMocker) -> None: "description": "Test task", "expected_output": "Test output", } - mock_agent = crew_mocker.MagicMock() + mock_agent = fabric_mocker.MagicMock() result = runner.build_task(task_config, mock_agent) @@ -836,18 +836,18 @@ def test_build_task_returns_dict(self, crew_mocker: CrewMocker) -> None: assert result["expected_output"] == "Test output" assert result["agent"] == mock_agent - def test_combines_agent_prompts(self, crew_mocker: CrewMocker) -> None: + def test_combines_agent_prompts(self, fabric_mocker: FabricMocker) -> None: """Test that multiple agent capabilities are combined into system prompt.""" - crew_mocker.mock_strands() + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner - MockAgent = crew_mocker.patch_strands_agent() + MockAgent = fabric_mocker.patch_strands_agent() runner = StrandsRunner() - crew_config = { - "description": "Multi-capability crew", + fabric_agent_config = { + "description": "Multi-capability fabric_agent", "agents": { "researcher": {"role": "Researcher", "goal": "Research topics"}, "writer": {"role": "Writer", "goal": "Write content"}, @@ -855,7 +855,7 @@ def test_combines_agent_prompts(self, crew_mocker: CrewMocker) -> None: "tasks": {}, } - runner.build_crew(crew_config) + runner.build_fabric_agent(fabric_agent_config) call_kwargs = MockAgent.call_args[1] system_prompt = call_kwargs["system_prompt"] @@ -866,19 +866,19 @@ def test_combines_agent_prompts(self, crew_mocker: CrewMocker) -> None: assert "Research topics" in system_prompt assert "Write content" in system_prompt - def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> None: - """Configured agent tools should be adapted for Strands crews.""" - crew_mocker.mock_strands() + def test_build_fabric_agent_resolves_declared_tools(self, fabric_mocker: FabricMocker) -> None: + """Configured agent tools should be adapted for Strands fabric_agents.""" + fabric_mocker.mock_strands() from agentic_fabric.runners.strands_runner import StrandsRunner - MockAgent = crew_mocker.patch_strands_agent() - resolved_tool = crew_mocker.MagicMock() - crew_mocker.patch("agentic_fabric.runners.strands_runner.resolve_strands_tools", return_value=[resolved_tool]) + MockAgent = fabric_mocker.patch_strands_agent() + resolved_tool = fabric_mocker.MagicMock() + fabric_mocker.patch("agentic_fabric.runners.strands_runner.resolve_strands_tools", return_value=[resolved_tool]) runner = StrandsRunner() - crew_config = { - "description": "Test crew", + fabric_agent_config = { + "description": "Test fabric_agent", "agents": { "writer": { "tools": ["FileWriteTool"], @@ -887,7 +887,7 @@ def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> No "tasks": {}, } - runner.build_crew(crew_config) + runner.build_fabric_agent(fabric_agent_config) assert MockAgent.call_args[1]["tools"] == [resolved_tool] @@ -895,109 +895,109 @@ def test_build_crew_resolves_declared_tools(self, crew_mocker: CrewMocker) -> No class TestBaseRunner: """Tests for base runner interface.""" - def test_build_and_run_convenience_method(self, crew_mocker: CrewMocker) -> None: - """Test that build_and_run calls build_crew and run with correct args.""" + def test_build_and_run_convenience_method(self, fabric_mocker: FabricMocker) -> None: + """Test that build_and_run calls build_fabric_agent and run with correct args.""" from agentic_fabric.runners.base import BaseRunner class TestRunner(BaseRunner): # Define abstract methods so the class can be instantiated - def build_crew(self, crew_config: dict[str, Any]) -> Any: + def build_fabric_agent(self, fabric_agent_config: dict[str, Any]) -> Any: pass - def run(self, crew: Any, inputs: dict[str, Any]) -> str: + def run(self, fabric_agent: Any, inputs: dict[str, Any]) -> str: pass def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: - return crew_mocker.MagicMock() + return fabric_mocker.MagicMock() def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: - return crew_mocker.MagicMock() + return fabric_mocker.MagicMock() runner = TestRunner() # Mock the methods to verify they're called correctly - mock_crew_obj = crew_mocker.MagicMock() - runner.build_crew = crew_mocker.MagicMock(return_value=mock_crew_obj) - runner.run = crew_mocker.MagicMock(return_value="test result") + mock_fabric_agent_obj = fabric_mocker.MagicMock() + runner.build_fabric_agent = fabric_mocker.MagicMock(return_value=mock_fabric_agent_obj) + runner.run = fabric_mocker.MagicMock(return_value="test result") - crew_config = {"test": "config"} + fabric_agent_config = {"test": "config"} inputs = {"test": "input"} - result = runner.build_and_run(crew_config, inputs) + result = runner.build_and_run(fabric_agent_config, inputs) assert result == "test result" - runner.build_crew.assert_called_once_with(crew_config) - runner.run.assert_called_once_with(mock_crew_obj, inputs) + runner.build_fabric_agent.assert_called_once_with(fabric_agent_config) + runner.run.assert_called_once_with(mock_fabric_agent_obj, inputs) - def test_build_and_run_uses_empty_inputs_when_none(self, crew_mocker: CrewMocker) -> None: + def test_build_and_run_uses_empty_inputs_when_none(self, fabric_mocker: FabricMocker) -> None: """Test that build_and_run uses empty dict when inputs is None.""" from agentic_fabric.runners.base import BaseRunner class TestRunner(BaseRunner): - def build_crew(self, crew_config: dict[str, Any]) -> Any: - return crew_mocker.MagicMock() + def build_fabric_agent(self, fabric_agent_config: dict[str, Any]) -> Any: + return fabric_mocker.MagicMock() - def run(self, crew: Any, inputs: dict[str, Any]) -> str: + def run(self, fabric_agent: Any, inputs: dict[str, Any]) -> str: assert inputs == {} return "result" def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: - return crew_mocker.MagicMock() + return fabric_mocker.MagicMock() def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: - return crew_mocker.MagicMock() + return fabric_mocker.MagicMock() runner = TestRunner() result = runner.build_and_run({}, None) assert result == "result" - def test_get_llm_default_implementation(self, crew_mocker: CrewMocker) -> None: + def test_get_llm_default_implementation(self, fabric_mocker: FabricMocker) -> None: """Test that get_llm has a default implementation.""" - mock_get_llm = crew_mocker.patch_get_llm() - crew_mocker.patch("agentic_fabric.config.llm.DEFAULT_MODEL", "claude-sonnet-4-20250514") + mock_get_llm = fabric_mocker.patch_get_llm() + fabric_mocker.patch("agentic_fabric.config.llm.DEFAULT_MODEL", "claude-sonnet-4-20250514") from agentic_fabric.runners.base import BaseRunner class TestRunner(BaseRunner): - def build_crew(self, crew_config: dict[str, Any]) -> Any: - return crew_mocker.MagicMock() + def build_fabric_agent(self, fabric_agent_config: dict[str, Any]) -> Any: + return fabric_mocker.MagicMock() - def run(self, crew: Any, inputs: dict[str, Any]) -> str: + def run(self, fabric_agent: Any, inputs: dict[str, Any]) -> str: return "result" def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: - return crew_mocker.MagicMock() + return fabric_mocker.MagicMock() def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: - return crew_mocker.MagicMock() + return fabric_mocker.MagicMock() runner = TestRunner() llm = runner.get_llm() assert llm == mock_get_llm.return_value - def test_get_llm_returns_none_when_module_unavailable(self, crew_mocker: CrewMocker) -> None: + def test_get_llm_returns_none_when_module_unavailable(self, fabric_mocker: FabricMocker) -> None: """Test that get_llm returns None when llm module not available.""" from agentic_fabric.runners.base import BaseRunner class TestRunner(BaseRunner): - def build_crew(self, crew_config: dict[str, Any]) -> Any: - return crew_mocker.MagicMock() + def build_fabric_agent(self, fabric_agent_config: dict[str, Any]) -> Any: + return fabric_mocker.MagicMock() - def run(self, crew: Any, inputs: dict[str, Any]) -> str: + def run(self, fabric_agent: Any, inputs: dict[str, Any]) -> str: return "result" def build_agent(self, agent_config: dict[str, Any], tools: list | None = None) -> Any: - return crew_mocker.MagicMock() + return fabric_mocker.MagicMock() def build_task(self, task_config: dict[str, Any], agent: Any) -> Any: - return crew_mocker.MagicMock() + return fabric_mocker.MagicMock() runner = TestRunner() # Simulate that the agentic_fabric.config.llm module is not available - crew_mocker.patch.dict(sys.modules, {"agentic_fabric.config.llm": None}) + fabric_mocker.patch.dict(sys.modules, {"agentic_fabric.config.llm": None}) llm = runner.get_llm() assert llm is None diff --git a/packages/agentic-fabric/tests/test_tools_init.py b/packages/agentic-fabric/tests/test_tools_init.py index 33ccbac..895e44a 100644 --- a/packages/agentic-fabric/tests/test_tools_init.py +++ b/packages/agentic-fabric/tests/test_tools_init.py @@ -2,8 +2,6 @@ from __future__ import annotations -import builtins - from typing import Any import pytest @@ -75,33 +73,20 @@ def fake_load_attr(module_name: str, attr_name: str) -> type[FakeTool]: def test_get_all_tools_suppresses_unavailable_optional_tools(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(tools, "get_file_tools", lambda: ["file"]) monkeypatch.setattr(tools, "get_scraping_tools", lambda: (_ for _ in ()).throw(ImportError("missing scraping"))) - real_import = builtins.__import__ - - def fake_import(name: str, *args: Any, **kwargs: Any) -> Any: - if name == "mesh_toolkit.agent_tools.crewai": - raise ImportError("missing meshy") - return real_import(name, *args, **kwargs) - - monkeypatch.setattr(builtins, "__import__", fake_import) + monkeypatch.setattr(tools, "_load_attr", lambda *_: (_ for _ in ()).throw(RuntimeError("missing"))) assert tools.get_all_tools() == ["file"] -def test_get_all_tools_includes_meshy_tools_when_available(monkeypatch: pytest.MonkeyPatch) -> None: +def test_get_all_tools_includes_vendor_capability_tools_when_available(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(tools, "get_file_tools", lambda: ["file"]) monkeypatch.setattr(tools, "get_scraping_tools", lambda: ["scrape"]) - real_import = builtins.__import__ - class FakeMeshModule: - @staticmethod - def get_tools() -> list[str]: - return ["mesh"] + def fake_load_attr(module_name: str, attr_name: str) -> Any: + assert module_name == "agentic_fabric.tools.vendor" + assert attr_name == "vendor_capability_tools" + return lambda **_: ["vendor"] - def fake_import(name: str, *args: Any, **kwargs: Any) -> Any: - if name == "mesh_toolkit.agent_tools.crewai": - return FakeMeshModule - return real_import(name, *args, **kwargs) - - monkeypatch.setattr(builtins, "__import__", fake_import) + monkeypatch.setattr(tools, "_load_attr", fake_load_attr) - assert tools.get_all_tools() == ["file", "scrape", "mesh"] + assert tools.get_all_tools() == ["file", "scrape", "vendor"] diff --git a/packages/agentic-fabric/tests/test_yaml_config_errors.py b/packages/agentic-fabric/tests/test_yaml_config_errors.py index 49364f3..a8a62c9 100644 --- a/packages/agentic-fabric/tests/test_yaml_config_errors.py +++ b/packages/agentic-fabric/tests/test_yaml_config_errors.py @@ -10,7 +10,7 @@ import yaml from agentic_fabric.core.discovery import ( - get_crew_config, + get_fabric_agent_config, get_framework_from_config_dir, load_manifest, ) @@ -45,79 +45,79 @@ def test_invalid_yaml_raises_error(self, tmp_path: Path) -> None: with pytest.raises(yaml.YAMLError): load_manifest(tmp_path) - def test_manifest_with_no_crews_key(self, tmp_path: Path) -> None: - """Manifest without 'crews' key should parse without error.""" + def test_manifest_with_no_fabric_agents_key(self, tmp_path: Path) -> None: + """Manifest without 'fabric_agents' key should parse without error.""" manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text("name: test_package\ndescription: A test\n") result = load_manifest(tmp_path) assert result.get("name") == "test_package" - assert result.get("crews") is None + assert result.get("fabric_agents") is None -class TestGetCrewConfigErrors: - """Test error handling in get_crew_config.""" +class TestGetFabricAgentConfigErrors: + """Test error handling in get_fabric_agent_config.""" - def test_crew_not_in_manifest_raises_value_error(self, tmp_path: Path) -> None: - """Requesting a non-existent crew should raise ValueError.""" + def test_fabric_agent_not_in_manifest_raises_value_error(self, tmp_path: Path) -> None: + """Requesting a non-existent fabric_agent should raise ValueError.""" manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " existing_crew:\n" - " description: An existing crew\n" - " agents: crews/existing/agents.yaml\n" - " tasks: crews/existing/tasks.yaml\n" + "fabric_agents:\n" + " existing_fabric_agent:\n" + " description: An existing fabric_agent\n" + " agents: fabric_agents/existing/agents.yaml\n" + " tasks: fabric_agents/existing/tasks.yaml\n" ) - with pytest.raises(ValueError, match="Crew 'nonexistent' not found"): - get_crew_config(tmp_path, "nonexistent") + with pytest.raises(ValueError, match="Fabric agent 'nonexistent' not found"): + get_fabric_agent_config(tmp_path, "nonexistent") - def test_error_lists_available_crews(self, tmp_path: Path) -> None: - """ValueError message should list available crew names.""" + def test_error_lists_available_fabric_agents(self, tmp_path: Path) -> None: + """ValueError message should list available fabric_agent names.""" manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " alpha_crew:\n" + "fabric_agents:\n" + " alpha_fabric_agent:\n" " agents: a.yaml\n" " tasks: t.yaml\n" - " beta_crew:\n" + " beta_fabric_agent:\n" " agents: a.yaml\n" " tasks: t.yaml\n" ) - with pytest.raises(ValueError, match="alpha_crew") as exc_info: - get_crew_config(tmp_path, "missing") - assert "beta_crew" in str(exc_info.value) + with pytest.raises(ValueError, match="alpha_fabric_agent") as exc_info: + get_fabric_agent_config(tmp_path, "missing") + assert "beta_fabric_agent" in str(exc_info.value) def test_missing_agents_yaml_returns_empty_dict(self, tmp_path: Path) -> None: """If agents YAML file doesn't exist, agents should be empty dict.""" manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " test_crew:\n" + "fabric_agents:\n" + " test_fabric_agent:\n" " description: Test\n" " agents: nonexistent_agents.yaml\n" " tasks: nonexistent_tasks.yaml\n" ) - config = get_crew_config(tmp_path, "test_crew") + config = get_fabric_agent_config(tmp_path, "test_fabric_agent") assert config["agents"] == {} def test_missing_tasks_yaml_returns_empty_dict(self, tmp_path: Path) -> None: """If tasks YAML file doesn't exist, tasks should be empty dict.""" manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " test_crew:\n" + "fabric_agents:\n" + " test_fabric_agent:\n" " description: Test\n" " agents: nonexistent_agents.yaml\n" " tasks: nonexistent_tasks.yaml\n" ) - config = get_crew_config(tmp_path, "test_crew") + config = get_fabric_agent_config(tmp_path, "test_fabric_agent") assert config["tasks"] == {} def test_agents_and_tasks_yaml_are_read_as_utf8(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: """Agents and tasks YAML should use explicit UTF-8 decoding.""" manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " test_crew:\n" + "fabric_agents:\n" + " test_fabric_agent:\n" " agents: agents.yaml\n" " tasks: tasks.yaml\n", encoding="utf-8", @@ -134,18 +134,18 @@ def tracking_read_text(self: Path, *args, **kwargs) -> str: monkeypatch.setattr(Path, "read_text", tracking_read_text) - config = get_crew_config(tmp_path, "test_crew") + config = get_fabric_agent_config(tmp_path, "test_fabric_agent") assert config["agents"]["agent"]["role"] == "Café reviewer" assert config["tasks"]["task"]["description"] == "Résumé review" assert encodings == ["utf-8", "utf-8"] - def test_empty_crews_section_raises_for_any_crew(self, tmp_path: Path) -> None: - """Empty crews section should raise ValueError.""" + def test_empty_fabric_agents_section_raises_for_any_fabric_agent(self, tmp_path: Path) -> None: + """Empty fabric_agents section should raise ValueError.""" manifest_file = tmp_path / "manifest.yaml" - manifest_file.write_text("crews: {}\n") + manifest_file.write_text("fabric_agents: {}\n") with pytest.raises(ValueError, match="not found"): - get_crew_config(tmp_path, "anything") + get_fabric_agent_config(tmp_path, "anything") def test_knowledge_paths_resolves_relative(self, tmp_path: Path) -> None: """Knowledge paths should resolve relative to config dir.""" @@ -156,9 +156,9 @@ def test_knowledge_paths_resolves_relative(self, tmp_path: Path) -> None: manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text( - "crews:\n test_crew:\n agents: agents.yaml\n tasks: tasks.yaml\n knowledge:\n - knowledge\n" + "fabric_agents:\n test_fabric_agent:\n agents: agents.yaml\n tasks: tasks.yaml\n knowledge:\n - knowledge\n" ) - config = get_crew_config(tmp_path, "test_crew") + config = get_fabric_agent_config(tmp_path, "test_fabric_agent") assert len(config["knowledge_paths"]) == 1 assert config["knowledge_paths"][0] == knowledge_dir @@ -166,14 +166,14 @@ def test_nonexistent_knowledge_paths_excluded(self, tmp_path: Path) -> None: """Non-existent knowledge paths should be silently excluded.""" manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " test_crew:\n" + "fabric_agents:\n" + " test_fabric_agent:\n" " agents: agents.yaml\n" " tasks: tasks.yaml\n" " knowledge:\n" " - missing_dir\n" ) - config = get_crew_config(tmp_path, "test_crew") + config = get_fabric_agent_config(tmp_path, "test_fabric_agent") assert config["knowledge_paths"] == [] def test_knowledge_file_paths_excluded(self, tmp_path: Path) -> None: @@ -181,15 +181,15 @@ def test_knowledge_file_paths_excluded(self, tmp_path: Path) -> None: (tmp_path / "knowledge.md").write_text("# Not a directory") manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " test_crew:\n" + "fabric_agents:\n" + " test_fabric_agent:\n" " agents: agents.yaml\n" " tasks: tasks.yaml\n" " knowledge:\n" " - knowledge.md\n" ) - config = get_crew_config(tmp_path, "test_crew") + config = get_fabric_agent_config(tmp_path, "test_fabric_agent") assert config["knowledge_paths"] == [] @@ -197,18 +197,18 @@ def test_agents_path_cannot_escape_config_dir(self, tmp_path: Path) -> None: """Manifest agents paths must stay inside the config directory.""" manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " test_crew:\n" + "fabric_agents:\n" + " test_fabric_agent:\n" " agents: ../outside_agents.yaml\n" " tasks: tasks.yaml\n" ) with pytest.raises(ValueError, match="Manifest path must be relative"): - get_crew_config(tmp_path, "test_crew") + get_fabric_agent_config(tmp_path, "test_fabric_agent") def test_agents_symlink_path_cannot_escape_config_dir(self, tmp_path: Path) -> None: """Resolved symlinks must not escape the config directory.""" - config_dir = tmp_path / ".crew" + config_dir = tmp_path / ".fabric" config_dir.mkdir() outside = tmp_path / "outside_agents.yaml" outside.write_text("agent: {}\n", encoding="utf-8") @@ -220,22 +220,22 @@ def test_agents_symlink_path_cannot_escape_config_dir(self, tmp_path: Path) -> N manifest_file = config_dir / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " test_crew:\n" + "fabric_agents:\n" + " test_fabric_agent:\n" " agents: agents-link.yaml\n" " tasks: tasks.yaml\n", encoding="utf-8", ) with pytest.raises(ValueError, match="Manifest path escapes config directory"): - get_crew_config(config_dir, "test_crew") + get_fabric_agent_config(config_dir, "test_fabric_agent") def test_knowledge_path_cannot_escape_config_dir(self, tmp_path: Path) -> None: """Manifest knowledge paths must stay inside the config directory.""" manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " test_crew:\n" + "fabric_agents:\n" + " test_fabric_agent:\n" " agents: agents.yaml\n" " tasks: tasks.yaml\n" " knowledge:\n" @@ -243,7 +243,7 @@ def test_knowledge_path_cannot_escape_config_dir(self, tmp_path: Path) -> None: ) with pytest.raises(ValueError, match="Manifest path must be relative"): - get_crew_config(tmp_path, "test_crew") + get_fabric_agent_config(tmp_path, "test_fabric_agent") class TestGetFrameworkFromConfigDir: @@ -262,7 +262,7 @@ def test_non_hidden_directory_returns_none(self) -> None: def test_all_known_dirs_return_expected_framework(self) -> None: """All known framework directories should map correctly.""" expected = { - ".crew": None, + ".fabric": None, ".crewai": "crewai", ".langgraph": "langgraph", ".strands": "strands", @@ -273,7 +273,7 @@ def test_all_known_dirs_return_expected_framework(self) -> None: class TestCrewConfigFrameworkConflict: - """Test framework conflict warnings in get_crew_config.""" + """Test framework conflict warnings in get_fabric_agent_config.""" def test_framework_mismatch_logs_warning(self, tmp_path: Path, caplog) -> None: """When manifest preferred_framework differs from directory, warn.""" @@ -282,15 +282,15 @@ def test_framework_mismatch_logs_warning(self, tmp_path: Path, caplog) -> None: manifest_file = crewai_dir / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " test_crew:\n" + "fabric_agents:\n" + " test_fabric_agent:\n" " description: Test\n" " agents: agents.yaml\n" " tasks: tasks.yaml\n" " preferred_framework: strands\n" ) with caplog.at_level(logging.WARNING): - config = get_crew_config(crewai_dir, "test_crew") + config = get_fabric_agent_config(crewai_dir, "test_fabric_agent") assert "preferred_framework=strands" in caplog.text assert "requires crewai" in caplog.text # required_framework should still be crewai (directory wins) @@ -303,15 +303,15 @@ def test_matching_preferred_framework_no_warning(self, tmp_path: Path, caplog) - manifest_file = crewai_dir / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " test_crew:\n" + "fabric_agents:\n" + " test_fabric_agent:\n" " description: Test\n" " agents: agents.yaml\n" " tasks: tasks.yaml\n" " preferred_framework: crewai\n" ) with caplog.at_level(logging.WARNING): - get_crew_config(crewai_dir, "test_crew") + get_fabric_agent_config(crewai_dir, "test_fabric_agent") assert "preferred_framework" not in caplog.text def test_preferred_auto_no_warning(self, tmp_path: Path, caplog) -> None: @@ -321,13 +321,13 @@ def test_preferred_auto_no_warning(self, tmp_path: Path, caplog) -> None: manifest_file = crewai_dir / "manifest.yaml" manifest_file.write_text( - "crews:\n" - " test_crew:\n" + "fabric_agents:\n" + " test_fabric_agent:\n" " description: Test\n" " agents: agents.yaml\n" " tasks: tasks.yaml\n" " preferred_framework: auto\n" ) with caplog.at_level(logging.WARNING): - get_crew_config(crewai_dir, "test_crew") + get_fabric_agent_config(crewai_dir, "test_fabric_agent") assert "preferred_framework" not in caplog.text diff --git a/packages/pytest-agentic-fabric/README.md b/packages/pytest-agentic-fabric/README.md index 609302b..0081323 100644 --- a/packages/pytest-agentic-fabric/README.md +++ b/packages/pytest-agentic-fabric/README.md @@ -8,8 +8,8 @@ Fixtures: - `agentic_runtime_registry`: isolated mutable registry for tests. - `agentic_runtime_modules`: known runtime import module names. - `agentic_mock_runtime`: installs fake runtime modules into `sys.modules`. -- `agentic_crew_config`: minimal framework-neutral crew config. -- `agentic_workspace`: temporary workspace with a discoverable `.crew` package. +- `agentic_fabric_agent_config`: minimal framework-neutral fabric agent config. +- `agentic_workspace`: temporary workspace with a discoverable `.fabric` package. Tests marked `@pytest.mark.agentic_e2e` or `@pytest.mark.agentic_runtime("crewai")` are skipped unless diff --git a/packages/pytest-agentic-fabric/pyproject.toml b/packages/pytest-agentic-fabric/pyproject.toml index a4651ab..9316485 100644 --- a/packages/pytest-agentic-fabric/pyproject.toml +++ b/packages/pytest-agentic-fabric/pyproject.toml @@ -28,6 +28,7 @@ dependencies = [ [project.optional-dependencies] tests = [ + "pytest-asyncio>=0.23.0", "pytest>=9.0.3", ] typing = [ @@ -54,3 +55,4 @@ packages = ["src/pytest_agentic_fabric"] addopts = ["-ra", "--strict-markers", "--strict-config"] testpaths = ["tests"] xfail_strict = true +asyncio_default_fixture_loop_scope = "function" diff --git a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py index 0322b6a..e6c06ea 100644 --- a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py +++ b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py @@ -108,11 +108,11 @@ def install(runtime: str) -> dict[str, ModuleType]: @pytest.fixture -def agentic_crew_config() -> dict[str, Any]: - """Return a minimal framework-neutral crew config.""" +def agentic_fabric_agent_config() -> dict[str, Any]: + """Return a minimal framework-neutral fabric agent config.""" return { - "name": "test_crew", - "description": "A test crew", + "name": "test_fabric_agent", + "description": "A test fabric agent", "agents": { "tester": { "role": "Tester", @@ -132,21 +132,21 @@ def agentic_crew_config() -> dict[str, Any]: @pytest.fixture -def agentic_workspace(tmp_path: Path, agentic_crew_config: dict[str, Any]) -> Path: - """Create a minimal workspace with a ``.crew`` package.""" +def agentic_workspace(tmp_path: Path, agentic_fabric_agent_config: dict[str, Any]) -> Path: + """Create a minimal workspace with a ``.fabric`` package.""" workspace = tmp_path / "workspace" - crew_dir = workspace / "packages" / "sample" / ".crew" - config_dir = crew_dir / "crews" / "test_crew" + fabric_dir = workspace / "packages" / "sample" / ".fabric" + config_dir = fabric_dir / "fabric_agents" / "test_fabric_agent" config_dir.mkdir(parents=True) - crew_dir.joinpath("manifest.yaml").write_text( + fabric_dir.joinpath("manifest.yaml").write_text( """ name: sample description: Test package -crews: - test_crew: - description: A test crew - agents: crews/test_crew/agents.yaml - tasks: crews/test_crew/tasks.yaml +fabric_agents: + test_fabric_agent: + description: A test fabric agent + agents: fabric_agents/test_fabric_agent/agents.yaml + tasks: fabric_agents/test_fabric_agent/tasks.yaml """.strip(), encoding="utf-8", ) @@ -168,5 +168,5 @@ def agentic_workspace(tmp_path: Path, agentic_crew_config: dict[str, Any]) -> Pa """.strip(), encoding="utf-8", ) - _ = agentic_crew_config + _ = agentic_fabric_agent_config return workspace diff --git a/packages/pytest-agentic-fabric/tests/test_plugin.py b/packages/pytest-agentic-fabric/tests/test_plugin.py index d9e7e3e..f22b78d 100644 --- a/packages/pytest-agentic-fabric/tests/test_plugin.py +++ b/packages/pytest-agentic-fabric/tests/test_plugin.py @@ -75,21 +75,22 @@ def test_agentic_mock_runtime_sets_runtime_entrypoints( assert hasattr(modules[runtime], expected_attr) -def test_agentic_crew_config_fixture(agentic_crew_config: dict[str, Any]) -> None: - """Minimal crew config should be usable by runtime tests.""" - assert agentic_crew_config["agents"]["tester"]["role"] == "Tester" - assert agentic_crew_config["tasks"]["verify"]["agent"] == "tester" +def test_agentic_fabric_agent_config_fixture(agentic_fabric_agent_config: dict[str, Any]) -> None: + """Minimal fabric agent config should be usable by runtime tests.""" + assert agentic_fabric_agent_config["agents"]["tester"]["role"] == "Tester" + assert agentic_fabric_agent_config["tasks"]["verify"]["agent"] == "tester" def test_agentic_workspace_fixture(agentic_workspace: Path) -> None: - """Workspace fixture should create a discoverable .crew package.""" - manifest = agentic_workspace / "packages" / "sample" / ".crew" / "manifest.yaml" + """Workspace fixture should create a discoverable .fabric package.""" + manifest = agentic_workspace / "packages" / "sample" / ".fabric" / "manifest.yaml" assert manifest.exists() - assert "test_crew" in manifest.read_text(encoding="utf-8") + assert "test_fabric_agent" in manifest.read_text(encoding="utf-8") def test_agentic_e2e_marker_skips_by_default(pytester: pytest.Pytester) -> None: """Runtime-dependent tests should skip unless --agentic-e2e is passed.""" + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function\n") pytester.makepyfile( """ import pytest @@ -107,6 +108,7 @@ def test_live_runtime(): def test_agentic_e2e_marker_runs_when_enabled(pytester: pytest.Pytester) -> None: """Runtime-dependent tests should run when --agentic-e2e is passed.""" + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function\n") pytester.makepyfile( """ import pytest diff --git a/pyproject.toml b/pyproject.toml index 1f794ff..1858ed3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,3 +25,19 @@ dev = [ "tox-gh>=1.3", "tox-uv>=1.11", ] + +[tool.pytest.ini_options] +addopts = ["-ra", "--strict-markers", "--strict-config"] +testpaths = [ + "packages/agentic-fabric/tests", + "packages/pytest-agentic-fabric/tests", +] +xfail_strict = true +asyncio_default_fixture_loop_scope = "function" +markers = [ + "crewai: tests that require CrewAI runtime dependencies", + "e2e: end-to-end tests that require external runtime dependencies", + "langgraph: tests that require LangGraph runtime dependencies", + "strands: tests that require Strands runtime dependencies", + "vendor_fabric: tests that require vendor-fabric runtime dependencies", +] diff --git a/uv.lock b/uv.lock index 5178e30..2101c32 100644 --- a/uv.lock +++ b/uv.lock @@ -1783,10 +1783,12 @@ dependencies = [ dev = [ { name = "mypy" }, { name = "pytest" }, + { name = "pytest-asyncio" }, { name = "ruff" }, ] tests = [ { name = "pytest" }, + { name = "pytest-asyncio" }, ] typing = [ { name = "mypy" }, @@ -1798,6 +1800,7 @@ requires-dist = [ { name = "pytest", specifier = ">=9.0.3" }, { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.0.3" }, { name = "pytest-agentic-fabric", extras = ["tests", "typing"], marker = "extra == 'dev'", editable = "packages/pytest-agentic-fabric" }, + { name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=0.23.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" }, ] provides-extras = ["tests", "typing", "dev"] From 6a715de1c2e1ebef09559c40361a7a9a4b364e50 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 03:57:40 -0500 Subject: [PATCH 20/35] fix: address review hardening feedback --- .github/workflows/automerge.yml | 5 +- .github/workflows/cd.yml | 11 ++- SECRETS_SYNC_ALIGNMENT.md | 2 +- .../src/agentic_fabric/core/discovery.py | 31 ++++++- .../agentic_fabric/tools/scraping_tools.py | 71 +++++++++------- .../agentic-fabric/tests/_fabric_mocker.py | 2 +- .../agentic-fabric/tests/test_discovery.py | 7 ++ .../tests/test_scraping_tool_crawler.py | 16 ++++ .../tests/test_yaml_config_errors.py | 81 +++++++++++++++++-- packages/pytest-agentic-fabric/pyproject.toml | 1 + .../src/pytest_agentic_fabric/plugin.py | 45 +++++------ .../tests/test_plugin.py | 63 ++++++++++++++- uv.lock | 2 + 13 files changed, 261 insertions(+), 76 deletions(-) diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index a6e4fac..6e12cf8 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -14,7 +14,10 @@ jobs: runs-on: ubuntu-latest if: > github.event.pull_request.user.login == 'dependabot[bot]' || - startsWith(github.event.pull_request.head.ref, 'release-please--') + ( + github.event.pull_request.user.login == 'github-actions[bot]' && + startsWith(github.event.pull_request.head.ref, 'release-please--') + ) steps: - name: Enable auto-merge (squash) env: diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 8e9aba6..8776341 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -81,12 +81,17 @@ jobs: version: "0.11.19" enable-cache: true - name: Validate package input + env: + PACKAGE: ${{ inputs.package }} run: | - case "${{ inputs.package }}" in + case "$PACKAGE" in agentic-fabric|pytest-agentic-fabric) ;; - *) echo "unsupported package: ${{ inputs.package }}" >&2; exit 1 ;; + *) echo "unsupported package: $PACKAGE" >&2; exit 1 ;; esac - - run: uv build --package "${{ inputs.package }}" --out-dir dist --clear + - name: Build package + env: + PACKAGE: ${{ inputs.package }} + run: uv build --package "$PACKAGE" --out-dir dist --clear - run: uv publish --trusted-publishing always dist/* publish-docs: diff --git a/SECRETS_SYNC_ALIGNMENT.md b/SECRETS_SYNC_ALIGNMENT.md index bead3d1..10de882 100644 --- a/SECRETS_SYNC_ALIGNMENT.md +++ b/SECRETS_SYNC_ALIGNMENT.md @@ -13,7 +13,7 @@ This file locks the intended SecretSync boundary for `agentic-fabric`. - PyPI distribution: `secrets-sync-python-binding` - Python import/module: `secrets_sync` -- This repository should not import `secretssync` directly; it should consume +- This repository should not import `secrets_sync` directly; it should consume SecretSync through `vendor-fabric`. ## This Repository's Role diff --git a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py index 89a101f..497ea0c 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py @@ -80,7 +80,10 @@ def discover_packages( # Determine which directories to look for if framework: dir_name = FRAMEWORK_TO_DIR.get(framework) - dirs_to_check = [dir_name] if dir_name else FRAMEWORK_DIRS + if dir_name is None: + msg = f"Unknown framework: {framework}" + raise ValueError(msg) + dirs_to_check = [dir_name] else: dirs_to_check = FRAMEWORK_DIRS @@ -176,7 +179,27 @@ def load_manifest(config_dir: Path) -> dict[str, Any]: manifest_path = config_dir / "manifest.yaml" with open(manifest_path, encoding="utf-8") as f: result = yaml.safe_load(f) - return result or {} + if result is None: + return {} + if not isinstance(result, dict): + msg = f"{manifest_path} must contain a mapping at the document root" + raise TypeError(msg) + return result + + +def _load_yaml_mapping(path: Path, description: str) -> dict[str, Any]: + """Load a required YAML mapping from disk.""" + if not path.is_file(): + msg = f"{description} file not found: {path}" + raise FileNotFoundError(msg) + + result = yaml.safe_load(path.read_text(encoding="utf-8")) + if result is None: + return {} + if not isinstance(result, dict): + msg = f"{path} must contain a mapping of {description.lower()}" + raise TypeError(msg) + return result def _resolve_config_path(config_dir: Path, relative_path: str) -> Path: @@ -241,8 +264,8 @@ def get_fabric_agent_config(config_dir: Path, fabric_agent_name: str) -> dict: agents_path = _resolve_config_path(config_dir, agents_rel) tasks_path = _resolve_config_path(config_dir, tasks_rel) - agents = yaml.safe_load(agents_path.read_text(encoding="utf-8")) if agents_path.exists() else {} - tasks = yaml.safe_load(tasks_path.read_text(encoding="utf-8")) if tasks_path.exists() else {} + agents = _load_yaml_mapping(agents_path, "Agents") + tasks = _load_yaml_mapping(tasks_path, "Tasks") # Resolve knowledge paths knowledge_paths = [] diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py b/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py index e6d4dbb..7d6ccb9 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/scraping_tools.py @@ -46,6 +46,22 @@ def _is_safe_crawl_url(url: str) -> bool: ) +def _get_safe_response(url: str) -> requests.Response | None: + """Request a safe URL without following redirects to unvalidated hosts.""" + try: + response = requests.get(url, timeout=30, allow_redirects=False) + response.raise_for_status() + except requests.RequestException: + log.exception("Error requesting %s", url) + return None + + status_code = getattr(response, "status_code", None) + if isinstance(status_code, int) and 300 <= status_code < 400: + log.warning("Skipping redirected URL %s", url) + return None + return response + + class ScrapeWebsiteTool: """A small first-party website scraping tool with CrewAI-compatible shape.""" @@ -58,11 +74,8 @@ def _run(self, url: str) -> str: log.warning("Skipping unsafe scrape URL %s", url) return "" - try: - response = requests.get(url, timeout=30) - response.raise_for_status() - except requests.RequestException: - log.exception("Error scraping %s", url) + response = _get_safe_response(url) + if response is None: return "" soup = BeautifulSoup(response.content, "html.parser") @@ -109,31 +122,27 @@ def _run(self, url: str) -> str: queued_urls.discard(current_url) visited_urls.add(current_url) - try: - response = requests.get(current_url, timeout=30) - response.raise_for_status() - - soup = BeautifulSoup(response.content, "html.parser") - page_content = self._scrape_content(soup) - if page_content: - scraped_content.append(page_content) - - if depth >= MAX_CRAWL_DEPTH: - continue - - for link in soup.find_all("a", href=True): - next_url = urljoin(current_url, link["href"]) - if ( - next_url not in visited_urls - and next_url not in queued_urls - and len(visited_urls) + len(queued_urls) < MAX_CRAWL_PAGES - and urlparse(next_url).netloc == base_netloc - and _is_safe_crawl_url(next_url) - ): - urls_to_visit.append((next_url, depth + 1)) - queued_urls.add(next_url) - - except requests.RequestException: - log.exception("Error crawling %s", current_url) + response = _get_safe_response(current_url) + if response is None: + continue + soup = BeautifulSoup(response.content, "html.parser") + page_content = self._scrape_content(soup) + if page_content: + scraped_content.append(page_content) + + if depth >= MAX_CRAWL_DEPTH: + continue + + for link in soup.find_all("a", href=True): + next_url = urljoin(current_url, link["href"]) + if ( + next_url not in visited_urls + and next_url not in queued_urls + and len(visited_urls) + len(queued_urls) < MAX_CRAWL_PAGES + and urlparse(next_url).netloc == base_netloc + and _is_safe_crawl_url(next_url) + ): + urls_to_visit.append((next_url, depth + 1)) + queued_urls.add(next_url) return " ".join(scraped_content) diff --git a/packages/agentic-fabric/tests/_fabric_mocker.py b/packages/agentic-fabric/tests/_fabric_mocker.py index 6a753b6..be72a64 100644 --- a/packages/agentic-fabric/tests/_fabric_mocker.py +++ b/packages/agentic-fabric/tests/_fabric_mocker.py @@ -399,7 +399,7 @@ def patch_get_fabric_agent_config(self, config: dict[str, Any] | None = None) -> The mock for get_fabric_agent_config. """ mock = self.mocker.patch("agentic_fabric.core.discovery.get_fabric_agent_config") - mock.return_value = config or {"name": "test", "agents": {}, "tasks": {}} + mock.return_value = {"name": "test", "agents": {}, "tasks": {}} if config is None else config return mock def patch_run_fabric_agent_auto(self, result: str = "Test result") -> Any: diff --git a/packages/agentic-fabric/tests/test_discovery.py b/packages/agentic-fabric/tests/test_discovery.py index ab525a8..d653ee6 100644 --- a/packages/agentic-fabric/tests/test_discovery.py +++ b/packages/agentic-fabric/tests/test_discovery.py @@ -65,6 +65,13 @@ def test_discover_packages_returns_empty_when_no_packages(self, tmp_path: Path) assert packages == {} + def test_discover_packages_rejects_unknown_framework(self, tmp_path: Path) -> None: + """Unknown framework filters should not widen discovery to all directories.""" + from agentic_fabric.core.discovery import discover_packages + + with pytest.raises(ValueError, match="Unknown framework: crewaii"): + discover_packages(workspace_root=tmp_path, framework="crewaii") + def test_discover_all_framework_configs(self, tmp_path: Path) -> None: """Test discovering all framework configs for a package.""" from agentic_fabric.core.discovery import discover_all_framework_configs diff --git a/packages/agentic-fabric/tests/test_scraping_tool_crawler.py b/packages/agentic-fabric/tests/test_scraping_tool_crawler.py index c0fb2d0..f50aa2c 100644 --- a/packages/agentic-fabric/tests/test_scraping_tool_crawler.py +++ b/packages/agentic-fabric/tests/test_scraping_tool_crawler.py @@ -82,6 +82,7 @@ def test_crawler_enforces_page_limit(monkeypatch: pytest.MonkeyPatch) -> None: """Crawl breadth should stay bounded.""" scraping_tools = import_scraping_tools(monkeypatch) monkeypatch.setattr(scraping_tools, "MAX_CRAWL_PAGES", 3) + monkeypatch.setattr(scraping_tools, "MAX_CRAWL_DEPTH", 2) root_response = MagicMock() root_response.content = b"".join([f"Page {index}".encode() for index in range(10)]) root_response.raise_for_status.return_value = None @@ -96,6 +97,21 @@ def test_crawler_enforces_page_limit(monkeypatch: pytest.MonkeyPatch) -> None: assert scraping_tools.requests.get.call_count == 3 +def test_scrapers_disable_redirects(monkeypatch: pytest.MonkeyPatch) -> None: + """Scraping requests should not follow redirects to unvalidated hosts.""" + scraping_tools = import_scraping_tools(monkeypatch) + response = MagicMock() + response.status_code = 302 + response.content = b"redirect body" + response.raise_for_status.return_value = None + scraping_tools.requests.get.return_value = response + + assert scraping_tools.ScrapeWebsiteTool()._run("https://example.test") == "" + assert scraping_tools.CrawlWebsiteTool()._run("https://example.test") == "" + assert scraping_tools.requests.get.call_args_list[0].kwargs["allow_redirects"] is False + assert scraping_tools.requests.get.call_args_list[1].kwargs["allow_redirects"] is False + + def test_crawler_enforces_depth_limit(monkeypatch: pytest.MonkeyPatch) -> None: """Crawl depth should prevent child requests when configured to zero.""" scraping_tools = import_scraping_tools(monkeypatch) diff --git a/packages/agentic-fabric/tests/test_yaml_config_errors.py b/packages/agentic-fabric/tests/test_yaml_config_errors.py index a8a62c9..b460ae1 100644 --- a/packages/agentic-fabric/tests/test_yaml_config_errors.py +++ b/packages/agentic-fabric/tests/test_yaml_config_errors.py @@ -16,6 +16,12 @@ ) +def write_empty_agent_task_files(config_dir: Path) -> None: + """Write minimal valid agents/tasks YAML files for config-loading tests.""" + (config_dir / "agents.yaml").write_text("{}\n", encoding="utf-8") + (config_dir / "tasks.yaml").write_text("{}\n", encoding="utf-8") + + class TestLoadManifestErrors: """Test error handling in load_manifest.""" @@ -45,6 +51,14 @@ def test_invalid_yaml_raises_error(self, tmp_path: Path) -> None: with pytest.raises(yaml.YAMLError): load_manifest(tmp_path) + def test_non_mapping_manifest_raises_type_error(self, tmp_path: Path) -> None: + """Manifest root must be a mapping.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text("- not\n- a mapping\n", encoding="utf-8") + + with pytest.raises(TypeError, match="must contain a mapping"): + load_manifest(tmp_path) + def test_manifest_with_no_fabric_agents_key(self, tmp_path: Path) -> None: """Manifest without 'fabric_agents' key should parse without error.""" manifest_file = tmp_path / "manifest.yaml" @@ -86,30 +100,74 @@ def test_error_lists_available_fabric_agents(self, tmp_path: Path) -> None: get_fabric_agent_config(tmp_path, "missing") assert "beta_fabric_agent" in str(exc_info.value) - def test_missing_agents_yaml_returns_empty_dict(self, tmp_path: Path) -> None: - """If agents YAML file doesn't exist, agents should be empty dict.""" + def test_missing_agents_yaml_raises_file_not_found(self, tmp_path: Path) -> None: + """Missing agents YAML should fail as a manifest contract error.""" manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text( "fabric_agents:\n" " test_fabric_agent:\n" " description: Test\n" " agents: nonexistent_agents.yaml\n" - " tasks: nonexistent_tasks.yaml\n" + " tasks: tasks.yaml\n" ) - config = get_fabric_agent_config(tmp_path, "test_fabric_agent") - assert config["agents"] == {} + (tmp_path / "tasks.yaml").write_text("{}\n", encoding="utf-8") - def test_missing_tasks_yaml_returns_empty_dict(self, tmp_path: Path) -> None: - """If tasks YAML file doesn't exist, tasks should be empty dict.""" + with pytest.raises(FileNotFoundError, match="Agents file not found"): + get_fabric_agent_config(tmp_path, "test_fabric_agent") + + def test_missing_tasks_yaml_raises_file_not_found(self, tmp_path: Path) -> None: + """Missing tasks YAML should fail as a manifest contract error.""" manifest_file = tmp_path / "manifest.yaml" manifest_file.write_text( "fabric_agents:\n" " test_fabric_agent:\n" " description: Test\n" - " agents: nonexistent_agents.yaml\n" + " agents: agents.yaml\n" " tasks: nonexistent_tasks.yaml\n" ) + (tmp_path / "agents.yaml").write_text("{}\n", encoding="utf-8") + + with pytest.raises(FileNotFoundError, match="Tasks file not found"): + get_fabric_agent_config(tmp_path, "test_fabric_agent") + + def test_non_mapping_agents_or_tasks_yaml_raises_type_error(self, tmp_path: Path) -> None: + """Agents and tasks YAML roots must be mappings.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text( + "fabric_agents:\n" + " test_fabric_agent:\n" + " agents: agents.yaml\n" + " tasks: tasks.yaml\n", + encoding="utf-8", + ) + (tmp_path / "agents.yaml").write_text("- not\n- a mapping\n", encoding="utf-8") + (tmp_path / "tasks.yaml").write_text("{}\n", encoding="utf-8") + + with pytest.raises(TypeError, match="mapping of agents"): + get_fabric_agent_config(tmp_path, "test_fabric_agent") + + (tmp_path / "agents.yaml").write_text("{}\n", encoding="utf-8") + (tmp_path / "tasks.yaml").write_text("- not\n- a mapping\n", encoding="utf-8") + + with pytest.raises(TypeError, match="mapping of tasks"): + get_fabric_agent_config(tmp_path, "test_fabric_agent") + + def test_empty_agents_and_tasks_yaml_return_empty_mappings(self, tmp_path: Path) -> None: + """Empty but present agents/tasks files should load as empty mappings.""" + manifest_file = tmp_path / "manifest.yaml" + manifest_file.write_text( + "fabric_agents:\n" + " test_fabric_agent:\n" + " agents: agents.yaml\n" + " tasks: tasks.yaml\n", + encoding="utf-8", + ) + (tmp_path / "agents.yaml").write_text("", encoding="utf-8") + (tmp_path / "tasks.yaml").write_text("", encoding="utf-8") + config = get_fabric_agent_config(tmp_path, "test_fabric_agent") + + assert config["agents"] == {} assert config["tasks"] == {} def test_agents_and_tasks_yaml_are_read_as_utf8(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: @@ -158,6 +216,7 @@ def test_knowledge_paths_resolves_relative(self, tmp_path: Path) -> None: manifest_file.write_text( "fabric_agents:\n test_fabric_agent:\n agents: agents.yaml\n tasks: tasks.yaml\n knowledge:\n - knowledge\n" ) + write_empty_agent_task_files(tmp_path) config = get_fabric_agent_config(tmp_path, "test_fabric_agent") assert len(config["knowledge_paths"]) == 1 assert config["knowledge_paths"][0] == knowledge_dir @@ -173,6 +232,7 @@ def test_nonexistent_knowledge_paths_excluded(self, tmp_path: Path) -> None: " knowledge:\n" " - missing_dir\n" ) + write_empty_agent_task_files(tmp_path) config = get_fabric_agent_config(tmp_path, "test_fabric_agent") assert config["knowledge_paths"] == [] @@ -188,6 +248,7 @@ def test_knowledge_file_paths_excluded(self, tmp_path: Path) -> None: " knowledge:\n" " - knowledge.md\n" ) + write_empty_agent_task_files(tmp_path) config = get_fabric_agent_config(tmp_path, "test_fabric_agent") @@ -241,6 +302,7 @@ def test_knowledge_path_cannot_escape_config_dir(self, tmp_path: Path) -> None: " knowledge:\n" " - ../knowledge\n" ) + write_empty_agent_task_files(tmp_path) with pytest.raises(ValueError, match="Manifest path must be relative"): get_fabric_agent_config(tmp_path, "test_fabric_agent") @@ -289,6 +351,7 @@ def test_framework_mismatch_logs_warning(self, tmp_path: Path, caplog) -> None: " tasks: tasks.yaml\n" " preferred_framework: strands\n" ) + write_empty_agent_task_files(crewai_dir) with caplog.at_level(logging.WARNING): config = get_fabric_agent_config(crewai_dir, "test_fabric_agent") assert "preferred_framework=strands" in caplog.text @@ -310,6 +373,7 @@ def test_matching_preferred_framework_no_warning(self, tmp_path: Path, caplog) - " tasks: tasks.yaml\n" " preferred_framework: crewai\n" ) + write_empty_agent_task_files(crewai_dir) with caplog.at_level(logging.WARNING): get_fabric_agent_config(crewai_dir, "test_fabric_agent") assert "preferred_framework" not in caplog.text @@ -328,6 +392,7 @@ def test_preferred_auto_no_warning(self, tmp_path: Path, caplog) -> None: " tasks: tasks.yaml\n" " preferred_framework: auto\n" ) + write_empty_agent_task_files(crewai_dir) with caplog.at_level(logging.WARNING): get_fabric_agent_config(crewai_dir, "test_fabric_agent") assert "preferred_framework" not in caplog.text diff --git a/packages/pytest-agentic-fabric/pyproject.toml b/packages/pytest-agentic-fabric/pyproject.toml index 9316485..216c53c 100644 --- a/packages/pytest-agentic-fabric/pyproject.toml +++ b/packages/pytest-agentic-fabric/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ ] dependencies = [ "pytest>=9.0.3", + "pyyaml>=6.0.3", ] [project.optional-dependencies] diff --git a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py index e6c06ea..0770374 100644 --- a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py +++ b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py @@ -9,6 +9,7 @@ from typing import Any import pytest +import yaml RUNTIME_MODULES: dict[str, tuple[str, ...]] = { @@ -136,37 +137,29 @@ def agentic_workspace(tmp_path: Path, agentic_fabric_agent_config: dict[str, Any """Create a minimal workspace with a ``.fabric`` package.""" workspace = tmp_path / "workspace" fabric_dir = workspace / "packages" / "sample" / ".fabric" - config_dir = fabric_dir / "fabric_agents" / "test_fabric_agent" + fabric_agent_name = str(agentic_fabric_agent_config.get("name", "test_fabric_agent")) + config_dir = fabric_dir / "fabric_agents" / fabric_agent_name config_dir.mkdir(parents=True) - fabric_dir.joinpath("manifest.yaml").write_text( - """ -name: sample -description: Test package -fabric_agents: - test_fabric_agent: - description: A test fabric agent - agents: fabric_agents/test_fabric_agent/agents.yaml - tasks: fabric_agents/test_fabric_agent/tasks.yaml -""".strip(), - encoding="utf-8", - ) + manifest_agent_config: dict[str, Any] = { + "description": agentic_fabric_agent_config.get("description", ""), + "agents": f"fabric_agents/{fabric_agent_name}/agents.yaml", + "tasks": f"fabric_agents/{fabric_agent_name}/tasks.yaml", + } + if "preferred_framework" in agentic_fabric_agent_config: + manifest_agent_config["preferred_framework"] = agentic_fabric_agent_config["preferred_framework"] + + manifest = { + "name": "sample", + "description": "Test package", + "fabric_agents": {fabric_agent_name: manifest_agent_config}, + } + fabric_dir.joinpath("manifest.yaml").write_text(yaml.safe_dump(manifest, sort_keys=False), encoding="utf-8") config_dir.joinpath("agents.yaml").write_text( - """ -tester: - role: Tester - goal: Verify behavior - backstory: Careful runtime test agent. -""".strip(), + yaml.safe_dump(agentic_fabric_agent_config.get("agents", {}), sort_keys=False), encoding="utf-8", ) config_dir.joinpath("tasks.yaml").write_text( - """ -verify: - description: Verify the requested behavior. - expected_output: A concise result - agent: tester -""".strip(), + yaml.safe_dump(agentic_fabric_agent_config.get("tasks", {}), sort_keys=False), encoding="utf-8", ) - _ = agentic_fabric_agent_config return workspace diff --git a/packages/pytest-agentic-fabric/tests/test_plugin.py b/packages/pytest-agentic-fabric/tests/test_plugin.py index f22b78d..fe6c637 100644 --- a/packages/pytest-agentic-fabric/tests/test_plugin.py +++ b/packages/pytest-agentic-fabric/tests/test_plugin.py @@ -12,6 +12,7 @@ from typing import Any import pytest +import yaml pytest_plugins = ("pytester",) @@ -84,8 +85,68 @@ def test_agentic_fabric_agent_config_fixture(agentic_fabric_agent_config: dict[s def test_agentic_workspace_fixture(agentic_workspace: Path) -> None: """Workspace fixture should create a discoverable .fabric package.""" manifest = agentic_workspace / "packages" / "sample" / ".fabric" / "manifest.yaml" + agents = agentic_workspace / "packages" / "sample" / ".fabric" / "fabric_agents" / "test_fabric_agent" / "agents.yaml" + tasks = agentic_workspace / "packages" / "sample" / ".fabric" / "fabric_agents" / "test_fabric_agent" / "tasks.yaml" + assert manifest.exists() - assert "test_fabric_agent" in manifest.read_text(encoding="utf-8") + assert yaml.safe_load(manifest.read_text(encoding="utf-8"))["fabric_agents"]["test_fabric_agent"][ + "description" + ] == "A test fabric agent" + assert yaml.safe_load(agents.read_text(encoding="utf-8"))["tester"]["role"] == "Tester" + assert yaml.safe_load(tasks.read_text(encoding="utf-8"))["verify"]["agent"] == "tester" + + +def test_agentic_workspace_fixture_uses_overridden_config(pytester: pytest.Pytester) -> None: + """Workspace fixture should serialize overridden fabric agent configs.""" + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function\n") + pytester.makeconftest( + """ + import pytest + + @pytest.fixture + def agentic_fabric_agent_config(): + return { + "name": "custom_agent", + "description": "Custom description", + "preferred_framework": "langgraph", + "agents": { + "custom": { + "role": "Custom", + "goal": "Check override", + "backstory": "Fixture override", + }, + }, + "tasks": { + "custom_task": { + "description": "Use override", + "expected_output": "Override result", + "agent": "custom", + }, + }, + } + """ + ) + pytester.makepyfile( + """ + import yaml + + def test_workspace_uses_override(agentic_workspace): + fabric_dir = agentic_workspace / "packages" / "sample" / ".fabric" + manifest = yaml.safe_load((fabric_dir / "manifest.yaml").read_text(encoding="utf-8")) + agent_config = manifest["fabric_agents"]["custom_agent"] + agents = yaml.safe_load((fabric_dir / "fabric_agents" / "custom_agent" / "agents.yaml").read_text(encoding="utf-8")) + tasks = yaml.safe_load((fabric_dir / "fabric_agents" / "custom_agent" / "tasks.yaml").read_text(encoding="utf-8")) + + assert agent_config["description"] == "Custom description" + assert agent_config["preferred_framework"] == "langgraph" + assert agents["custom"]["role"] == "Custom" + assert tasks["custom_task"]["agent"] == "custom" + """ + ) + + result = pytester.runpytest("-q") + + result.assert_outcomes(passed=1) def test_agentic_e2e_marker_skips_by_default(pytester: pytest.Pytester) -> None: diff --git a/uv.lock b/uv.lock index 2101c32..530bbfa 100644 --- a/uv.lock +++ b/uv.lock @@ -1777,6 +1777,7 @@ version = "0.1.0" source = { editable = "packages/pytest-agentic-fabric" } dependencies = [ { name = "pytest" }, + { name = "pyyaml" }, ] [package.optional-dependencies] @@ -1801,6 +1802,7 @@ requires-dist = [ { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.0.3" }, { name = "pytest-agentic-fabric", extras = ["tests", "typing"], marker = "extra == 'dev'", editable = "packages/pytest-agentic-fabric" }, { name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=0.23.0" }, + { name = "pyyaml", specifier = ">=6.0.3" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" }, ] provides-extras = ["tests", "typing", "dev"] From 84a2f3a58ced46e6b293fc711b1335feb0a07f62 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 04:07:26 -0500 Subject: [PATCH 21/35] fix: preserve vendor MCP import diagnostics --- docs/agentic-workflows.rst | 4 ++- docs/vendor-fabric.rst | 4 ++- packages/agentic-fabric/README.md | 3 +++ .../src/agentic_fabric/tools/meshy_mcp.py | 10 +++++++- .../src/agentic_fabric/tools/vendor_mcp.py | 10 +++++++- .../agentic-fabric/tests/test_mcp_adapters.py | 25 +++++++++++++++++++ 6 files changed, 52 insertions(+), 4 deletions(-) diff --git a/docs/agentic-workflows.rst b/docs/agentic-workflows.rst index 6441818..744dd14 100644 --- a/docs/agentic-workflows.rst +++ b/docs/agentic-workflows.rst @@ -112,4 +112,6 @@ runtime-visible adapter layer: public ``vendor-fabric`` data methods. ``agentic-fabric-meshy-mcp`` adapts the Meshy capability definitions from ``vendor_fabric.meshy.tools`` into MCP tool metadata. Install the matching ``vendor-fabric`` package and provider extras -in the same environment before running provider-backed MCP tools. +in the same environment before running provider-backed MCP tools. Startup +errors include the install guidance plus the original import failure so a +missing provider extra is distinguishable from a missing adapter dependency. diff --git a/docs/vendor-fabric.rst b/docs/vendor-fabric.rst index 1ad2377..01eb114 100644 --- a/docs/vendor-fabric.rst +++ b/docs/vendor-fabric.rst @@ -49,4 +49,6 @@ MCP ownership follows the same boundary. ``agentic-fabric`` owns the points because MCP is an agent runtime transport. The underlying connector classes, provider methods, Meshy capability metadata, credentials, and network calls remain in ``vendor-fabric`` and are imported lazily only when the MCP -server is created. +server is created. If those lazy imports fail, the adapter reports the +``agentic-fabric[mcp]`` or ``vendor-fabric[...]`` install guidance and preserves +the original import failure for provider-extra debugging. diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index 1a259cc..2b791f0 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -176,6 +176,9 @@ public `vendor-fabric` data methods. `agentic-fabric-meshy-mcp` converts Meshy capability metadata from `vendor-fabric[meshy]` into MCP tools. Both servers import provider code lazily; install the matching `vendor-fabric` package/extras in the same environment before running provider-backed tools. +If provider startup fails, the adapter error includes the `agentic-fabric[mcp]` +or `vendor-fabric[...]` install guidance plus the original import failure so +missing provider extras are visible. ## Repository Boundary diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/meshy_mcp.py b/packages/agentic-fabric/src/agentic_fabric/tools/meshy_mcp.py index 1718cf5..8a174a8 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/meshy_mcp.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/meshy_mcp.py @@ -19,12 +19,20 @@ ) +def _install_error(message: str, error: ImportError) -> ImportError: + """Build install guidance without hiding the actual failed import.""" + detail = str(error) + if detail: + return ImportError(f"{message}\nOriginal import error: {detail}") + return ImportError(message) + + def _require_meshy_tool_definitions() -> list[dict[str, Any]]: """Load Meshy capability metadata from vendor-fabric lazily.""" try: from vendor_fabric.meshy.tools import TOOL_DEFINITIONS except ImportError as exc: - raise ImportError(VENDOR_INSTALL_MESSAGE) from exc + raise _install_error(VENDOR_INSTALL_MESSAGE, exc) from exc return list(TOOL_DEFINITIONS) diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/vendor_mcp.py b/packages/agentic-fabric/src/agentic_fabric/tools/vendor_mcp.py index 7f7e1df..2cc7d1c 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/vendor_mcp.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/vendor_mcp.py @@ -22,13 +22,21 @@ ) +def _install_error(message: str, error: ImportError) -> ImportError: + """Build install guidance without hiding the actual failed import.""" + detail = str(error) + if detail: + return ImportError(f"{message}\nOriginal import error: {detail}") + return ImportError(message) + + def _require_vendor_fabric() -> tuple[Any, Callable[..., Any]]: """Load vendor-fabric registry and surface helpers lazily.""" try: from vendor_fabric import registry from vendor_fabric.surface import connector_data_methods except ImportError as exc: - raise ImportError(VENDOR_INSTALL_MESSAGE) from exc + raise _install_error(VENDOR_INSTALL_MESSAGE, exc) from exc return registry, connector_data_methods diff --git a/packages/agentic-fabric/tests/test_mcp_adapters.py b/packages/agentic-fabric/tests/test_mcp_adapters.py index 9761808..2e2f76e 100644 --- a/packages/agentic-fabric/tests/test_mcp_adapters.py +++ b/packages/agentic-fabric/tests/test_mcp_adapters.py @@ -150,6 +150,17 @@ def test_meshy_mcp_reports_missing_vendor_fabric(monkeypatch: pytest.MonkeyPatch meshy_mcp.create_server() +def test_meshy_mcp_preserves_provider_import_error(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setitem(sys.modules, "vendor_fabric", types.ModuleType("vendor_fabric")) + monkeypatch.setitem(sys.modules, "vendor_fabric.meshy", types.ModuleType("vendor_fabric.meshy")) + + with pytest.raises(ImportError) as exc_info: + meshy_mcp._require_meshy_tool_definitions() + + assert "vendor-fabric[meshy] is required" in str(exc_info.value) + assert "Original import error:" in str(exc_info.value) + + def test_meshy_mcp_exposes_vendor_tool_definitions(monkeypatch: pytest.MonkeyPatch) -> None: install_fake_mcp(monkeypatch) @@ -224,6 +235,7 @@ def model_dump(self) -> dict[str, bool]: "properties": {}, "required": [], } + assert str(meshy_mcp._install_error("install it", ImportError())) == "install it" assert meshy_mcp._to_builtin(Dumpable()) == {"dumped": True} assert meshy_mcp._to_builtin([Dumpable()]) == [{"dumped": True}] assert meshy_mcp._jsonable_tool_result(Dumpable()) == {"dumped": True} @@ -346,6 +358,18 @@ def test_vendor_mcp_reports_missing_vendor_fabric(monkeypatch: pytest.MonkeyPatc vendor_mcp.create_server() +def test_vendor_mcp_preserves_provider_import_error(monkeypatch: pytest.MonkeyPatch) -> None: + vendor_module = types.ModuleType("vendor_fabric") + vendor_module.registry = types.SimpleNamespace() + monkeypatch.setitem(sys.modules, "vendor_fabric", vendor_module) + + with pytest.raises(ImportError) as exc_info: + vendor_mcp._require_vendor_fabric() + + assert "vendor-fabric is required" in str(exc_info.value) + assert "Original import error:" in str(exc_info.value) + + def test_vendor_mcp_exposes_catalog_and_connector_tools(monkeypatch: pytest.MonkeyPatch) -> None: install_fake_mcp(monkeypatch) install_fake_vendor_fabric(monkeypatch) @@ -412,6 +436,7 @@ def unresolved(value: MissingType) -> dict[str, Any]: # noqa: F821 assert schema["properties"]["count"]["description"] == "Count value." assert unresolved_schema["properties"]["value"]["type"] == "string" assert vendor_mcp._connector_classes(types.SimpleNamespace()) == {} + assert str(vendor_mcp._install_error("install it", ImportError())) == "install it" assert vendor_mcp._to_builtin(Dumpable()) == {"dumped": True} assert vendor_mcp._to_builtin([Dumpable()]) == [{"dumped": True}] assert vendor_mcp._jsonable_tool_result(Dumpable()) == {"dumped": True} From 26b6f44facd43e30dcad4189a8fab3de072f0a23 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 04:12:40 -0500 Subject: [PATCH 22/35] test: guard fabric boundary drift --- AGENTS.md | 4 +-- docs/architecture.rst | 21 ++++++++++----- docs/development.rst | 5 ++++ .../tests/test_import_surfaces.py | 27 +++++++++++++++++++ packages/pytest-agentic-fabric/README.md | 6 +++++ 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 977f7d5..36d2322 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -147,8 +147,8 @@ Before final validation, check local and remote state for both upstream repos. - Add passthrough `vendor-fabric` extras only after `vendor-fabric` exists on PyPI. As of this handoff, `vendor-fabric` has no resolvable PyPI release under that name, so declaring passthrough extras makes `uv lock` fail. -- Use configured logging or Python logging/warnings/exceptions; no runtime - `print()` paths. +- Use configured logging or Python logging/warnings/exceptions in library + runtime paths. CLI commands and examples may write user-facing output. ### Docs diff --git a/docs/architecture.rst b/docs/architecture.rst index ea72490..705f49f 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -93,13 +93,20 @@ compatibility API remains in ``agentic_fabric.core.decomposer``: ``detect_framework()``, ``get_runner()``, ``get_available_frameworks()``, and ``get_framework_info()``. -Runtime selection should follow explicit precedence when the caller does -not choose a runtime: - -1. a runtime already active on ``AgenticData`` -2. a runtime requested in the fabric agent manifest -3. the first installed runtime in documented priority order -4. a clear unavailable-runtime error with install guidance +Runtime selection follows this precedence: + +1. an explicit runtime argument from the caller +2. a runtime already active on ``AgenticData`` +3. a runtime required by the fabric agent manifest or framework-specific + config directory +4. the first installed runtime in documented priority order +5. a clear unavailable-runtime error with install guidance + +Manifest-required runtimes are also compatibility constraints. If a caller +explicitly requests ``langgraph`` for a fabric agent that requires +``crewai``, or an active runtime conflicts with that requirement, +``AgenticData`` raises a configuration error rather than silently selecting +another framework. Vendor Tooling -------------- diff --git a/docs/development.rst b/docs/development.rst index 8d6488d..c314563 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -26,5 +26,10 @@ Use ``pytest-agentic-fabric`` fixtures for reusable runtime test setup: agentic_mock_runtime("langgraph") assert agentic_fabric_agent_config["name"] == "test_fabric_agent" +Override ``agentic_fabric_agent_config`` when a test needs a custom +discoverable workspace. The ``agentic_workspace`` fixture writes that config +into a temporary ``packages/sample/.fabric`` tree with matching manifest, +agents, and tasks YAML files. + Before deleting old monorepo code, first prove this repository owns the moved surface with docs, tests, package metadata, and release workflows. diff --git a/packages/agentic-fabric/tests/test_import_surfaces.py b/packages/agentic-fabric/tests/test_import_surfaces.py index 2d9b1d3..7e7f9ca 100644 --- a/packages/agentic-fabric/tests/test_import_surfaces.py +++ b/packages/agentic-fabric/tests/test_import_surfaces.py @@ -7,9 +7,14 @@ import runpy import sys +from pathlib import Path + import pytest +PACKAGE_ROOT = Path(__file__).resolve().parents[1] + + def test_package_version_falls_back_when_not_installed(monkeypatch: pytest.MonkeyPatch) -> None: """Source-tree imports should have a stable version fallback.""" package_file = importlib.import_module("agentic_fabric").__file__ @@ -59,3 +64,25 @@ def test_cli_script_entrypoint_prints_help(monkeypatch: pytest.MonkeyPatch, caps runpy.run_path(str(cli_main.__file__), run_name="__main__") assert "agentic-fabric - framework-agnostic fabric agent runner" in capsys.readouterr().out + + +def test_runtime_source_does_not_reown_vendor_or_legacy_surfaces() -> None: + """Runtime source should not drift into vendor ownership or old names.""" + disallowed = { + "secrets_sync": "SecretSync Python access must route through vendor-fabric capabilities", + "secretssync": "SecretSync direct-import spelling is not part of this layer", + "otterfall": "otterfall is repo-specific and not part of the agnostic package", + "agentic-crew": "fabric agent naming replaced old crew package naming", + "connector_builder_crew": "connector builder code should use fabric naming", + "ConnectorBuilderCrew": "connector builder code should use fabric naming", + } + source_files = sorted((PACKAGE_ROOT / "src" / "agentic_fabric").rglob("*.py")) + + failures: list[str] = [] + for source_file in source_files: + text = source_file.read_text(encoding="utf-8") + for token, reason in disallowed.items(): + if token in text: + failures.append(f"{source_file.relative_to(PACKAGE_ROOT)} contains {token!r}: {reason}") + + assert failures == [] diff --git a/packages/pytest-agentic-fabric/README.md b/packages/pytest-agentic-fabric/README.md index 0081323..11c6e65 100644 --- a/packages/pytest-agentic-fabric/README.md +++ b/packages/pytest-agentic-fabric/README.md @@ -11,6 +11,12 @@ Fixtures: - `agentic_fabric_agent_config`: minimal framework-neutral fabric agent config. - `agentic_workspace`: temporary workspace with a discoverable `.fabric` package. +`agentic_workspace` serializes the current `agentic_fabric_agent_config` +fixture into `packages/sample/.fabric/manifest.yaml` plus matching +`agents.yaml` and `tasks.yaml` files. Override `agentic_fabric_agent_config` +in a test module or `conftest.py` to create a custom workspace while keeping +the discovery layout consistent. + Tests marked `@pytest.mark.agentic_e2e` or `@pytest.mark.agentic_runtime("crewai")` are skipped unless `--agentic-e2e` is passed. From 463a6c4f8938de51f4bc0b3cbdd75aef424898a1 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 04:17:21 -0500 Subject: [PATCH 23/35] ci: monitor workspace package dependencies --- .github/dependabot.yml | 16 ++++++++++++++++ .github/workflows/cd.yml | 6 +++--- .github/workflows/ci.yml | 4 ++-- AGENTS.md | 2 +- packages/agentic-fabric/README.md | 2 ++ 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ac1ce4a..9dd97e1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,6 +8,22 @@ updates: minor-and-patch: patterns: ["*"] update-types: ["minor", "patch"] + - package-ecosystem: pip + directory: /packages/agentic-fabric + schedule: + interval: weekly + groups: + minor-and-patch: + patterns: ["*"] + update-types: ["minor", "patch"] + - package-ecosystem: pip + directory: /packages/pytest-agentic-fabric + schedule: + interval: weekly + groups: + minor-and-patch: + patterns: ["*"] + update-types: ["minor", "patch"] - package-ecosystem: github-actions directory: / schedule: diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 8776341..8835354 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -36,7 +36,7 @@ jobs: with: version: "0.11.19" enable-cache: true - - run: uv sync --all-packages --dev + - run: uv sync --all-packages --all-extras --dev - run: uv tool install tox --with tox-uv --with tox-gh - run: tox -e "py$(echo '${{ matrix.python-version }}' | tr -d '.')" @@ -57,7 +57,7 @@ jobs: with: version: "0.11.19" enable-cache: true - - run: uv sync --all-packages --dev + - run: uv sync --all-packages --all-extras --dev - run: uv tool install tox --with tox-uv --with tox-gh - run: tox -e lint,typecheck,coverage,plugin,docs,build @@ -121,7 +121,7 @@ jobs: with: version: "0.11.19" enable-cache: true - - run: uv sync --all-packages --dev + - run: uv sync --all-packages --all-extras --dev - run: uv tool install tox --with tox-uv --with tox-gh - run: tox -e docs - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60c59da..650aabf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: with: version: "0.11.19" enable-cache: true - - run: uv sync --all-packages --dev + - run: uv sync --all-packages --all-extras --dev - run: uv tool install tox --with tox-uv --with tox-gh - run: tox -e "py$(echo '${{ matrix.python-version }}' | tr -d '.')" @@ -49,6 +49,6 @@ jobs: with: version: "0.11.19" enable-cache: true - - run: uv sync --all-packages --dev + - run: uv sync --all-packages --all-extras --dev - run: uv tool install tox --with tox-uv --with tox-gh - run: tox -e lint,typecheck,coverage,plugin,docs,build diff --git a/AGENTS.md b/AGENTS.md index 36d2322..496ed9e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -77,7 +77,7 @@ PyPI. The package currently has no resolvable PyPI release under that name. ## Preferred Commands ```bash -uv sync --all-packages +uv sync --all-packages --all-extras --dev tox -e lint tox -e typecheck tox -e py311,py312,py313,py314 diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index 2b791f0..0c3de79 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -205,6 +205,7 @@ tox -e docs Local validation: ```bash +uv sync --all-packages --all-extras --dev tox -e lint tox -e typecheck tox -e py311 @@ -212,6 +213,7 @@ tox -e py312 tox -e py313 tox -e py314 tox -e coverage +tox -e plugin tox -e build ``` From a9c73b4f45676f4bab99d8c7ebe5f424e223558a Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 04:22:21 -0500 Subject: [PATCH 24/35] test: guard retired vendor MCP entrypoints --- .../tests/test_import_surfaces.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/packages/agentic-fabric/tests/test_import_surfaces.py b/packages/agentic-fabric/tests/test_import_surfaces.py index 7e7f9ca..fce6a8e 100644 --- a/packages/agentic-fabric/tests/test_import_surfaces.py +++ b/packages/agentic-fabric/tests/test_import_surfaces.py @@ -13,6 +13,7 @@ PACKAGE_ROOT = Path(__file__).resolve().parents[1] +WORKSPACE_ROOT = PACKAGE_ROOT.parents[1] def test_package_version_falls_back_when_not_installed(monkeypatch: pytest.MonkeyPatch) -> None: @@ -86,3 +87,47 @@ def test_runtime_source_does_not_reown_vendor_or_legacy_surfaces() -> None: failures.append(f"{source_file.relative_to(PACKAGE_ROOT)} contains {token!r}: {reason}") assert failures == [] + + +def test_public_surfaces_do_not_advertise_retired_vendor_mcp_entrypoints() -> None: + """MCP ownership should stay with agentic-fabric, not vendor-fabric.""" + retired = { + "vendor_fabric.mcp": "use agentic_fabric.tools.vendor_mcp", + "vendor_fabric.meshy.mcp": "use agentic_fabric.tools.meshy_mcp", + "vendor-fabric-mcp": "use the agentic-fabric-vendor-mcp console script", + "vendor-fabric[mcp]": "MCP transport dependencies live in agentic-fabric[mcp]", + "vendor-fabric[meshy,mcp]": "combine agentic-fabric[mcp] with vendor-fabric[meshy]", + } + roots = [ + PACKAGE_ROOT / "README.md", + PACKAGE_ROOT / "pyproject.toml", + PACKAGE_ROOT / "src", + PACKAGE_ROOT / "examples", + WORKSPACE_ROOT / "docs", + WORKSPACE_ROOT / "AGENTS.md", + WORKSPACE_ROOT / "README.md", + WORKSPACE_ROOT / "pyproject.toml", + WORKSPACE_ROOT / "tox.ini", + ] + + files: list[Path] = [] + for root in roots: + if root.is_file(): + files.append(root) + elif root.is_dir(): + files.extend( + path + for path in root.rglob("*") + if path.suffix in {".md", ".py", ".rst", ".toml", ".yaml", ".yml"} + and "_build" not in path.parts + and "__pycache__" not in path.parts + ) + + failures: list[str] = [] + for source_file in sorted(files): + text = source_file.read_text(encoding="utf-8") + for token, replacement in retired.items(): + if token in text: + failures.append(f"{source_file.relative_to(WORKSPACE_ROOT)} contains {token!r}: {replacement}") + + assert failures == [] From 8c0b4464b487915d19391493180e404ee39e7fe5 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 04:26:08 -0500 Subject: [PATCH 25/35] ci: audit optional dependency set --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 2 +- AGENTS.md | 1 + docs/development.rst | 1 + packages/agentic-fabric/README.md | 1 + tox.ini | 11 +++++++++++ 6 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 8835354..e7afae6 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -59,7 +59,7 @@ jobs: enable-cache: true - run: uv sync --all-packages --all-extras --dev - run: uv tool install tox --with tox-uv --with tox-gh - - run: tox -e lint,typecheck,coverage,plugin,docs,build + - run: tox -e lint,typecheck,audit,coverage,plugin,docs,build publish-package: name: Publish package to PyPI diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 650aabf..1a38183 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,4 +51,4 @@ jobs: enable-cache: true - run: uv sync --all-packages --all-extras --dev - run: uv tool install tox --with tox-uv --with tox-gh - - run: tox -e lint,typecheck,coverage,plugin,docs,build + - run: tox -e lint,typecheck,audit,coverage,plugin,docs,build diff --git a/AGENTS.md b/AGENTS.md index 496ed9e..967cb8e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,6 +80,7 @@ PyPI. The package currently has no resolvable PyPI release under that name. uv sync --all-packages --all-extras --dev tox -e lint tox -e typecheck +tox -e audit tox -e py311,py312,py313,py314 tox -e coverage tox -e plugin diff --git a/docs/development.rst b/docs/development.rst index c314563..c62e626 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -7,6 +7,7 @@ Use tox for local validation: tox -e lint tox -e typecheck + tox -e audit tox -e py311 tox -e py312 tox -e py313 diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index 0c3de79..2fc6836 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -208,6 +208,7 @@ Local validation: uv sync --all-packages --all-extras --dev tox -e lint tox -e typecheck +tox -e audit tox -e py311 tox -e py312 tox -e py313 diff --git a/tox.ini b/tox.ini index 216a612..ef80847 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,7 @@ requires = env_list = lint typecheck + audit py311 py312 py313 @@ -60,6 +61,16 @@ deps = -e packages/pytest-agentic-fabric[tests] commands = pytest packages/pytest-agentic-fabric/tests {posargs} +[testenv:audit] +description = Audit resolved optional, test, and typing dependencies +runner = uv-venv-runner +package = skip +deps = + -e packages/agentic-fabric[langgraph,strands,mcp,scraping,tests,typing] + -e packages/pytest-agentic-fabric[tests,typing] + pip-audit==2.10.1 +commands = pip-audit --skip-editable + [testenv:coverage] description = Enforce full coverage for both publishable packages runner = uv-venv-runner From 79cc1d706707931a8149de1415464a97cb4c6f70 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 04:28:04 -0500 Subject: [PATCH 26/35] chore: refresh dependency lock --- uv.lock | 451 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 223 insertions(+), 228 deletions(-) diff --git a/uv.lock b/uv.lock index 530bbfa..7965e04 100644 --- a/uv.lock +++ b/uv.lock @@ -2,16 +2,11 @@ version = 1 revision = 3 requires-python = ">=3.11" resolution-markers = [ - "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15'", "python_full_version == '3.14.*' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version < '3.12' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform != 'win32'", - "python_full_version == '3.12.*' and sys_platform != 'win32'", - "python_full_version < '3.12' and sys_platform != 'win32'", + "python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version < '3.14' and sys_platform != 'win32'", ] [manifest] @@ -803,92 +798,92 @@ wheels = [ [[package]] name = "jiter" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/c1/0cddc6eb17d4c53a99840953f95dd3accdc5cfc7a337b0e9b26476276be9/jiter-0.14.0.tar.gz", hash = "sha256:e8a39e66dac7153cf3f964a12aad515afa8d74938ec5cc0018adcdae5367c79e", size = 165725, upload-time = "2026-04-10T14:28:42.01Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/1f/198ae537fccb7080a0ed655eb56abf64a92f79489dfbf79f40fa34225bcd/jiter-0.14.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7e791e247b8044512e070bd1f3633dc08350d32776d2d6e7473309d0edf256a2", size = 316896, upload-time = "2026-04-10T14:26:01.986Z" }, - { url = "https://files.pythonhosted.org/packages/cf/34/da67cff3fce964a36d03c3e365fb0f8726ade2a6cfd4d3c70107e216ead6/jiter-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71527ce13fd5a0c4e40ad37331f8c547177dbb2dd0a93e5278b6a5eecf748804", size = 321085, upload-time = "2026-04-10T14:26:03.364Z" }, - { url = "https://files.pythonhosted.org/packages/ed/36/4c72e67180d4e71a4f5dcf7886d0840e83c49ab11788172177a77570326e/jiter-0.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c4a7ab56f746014874f2c525584c0daca1dec37f66fd707ecef3b7e5c2228c", size = 347393, upload-time = "2026-04-10T14:26:05.314Z" }, - { url = "https://files.pythonhosted.org/packages/bc/db/9b39e09ceafa9878235c0fc29e3e3f9b12a4c6a98ea3085b998cadf3accc/jiter-0.14.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:376e9dafff914253bb9d46cdc5f7965607fbe7feb0a491c34e35f92b2770702e", size = 372937, upload-time = "2026-04-10T14:26:06.884Z" }, - { url = "https://files.pythonhosted.org/packages/b0/96/0dcba1d7a82c1b720774b48ef239376addbaf30df24c34742ac4a57b67b2/jiter-0.14.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23ad2a7a9da1935575c820428dd8d2490ce4d23189691ce33da1fc0a58e14e1c", size = 463646, upload-time = "2026-04-10T14:26:08.345Z" }, - { url = "https://files.pythonhosted.org/packages/f1/e3/f61b71543e746e6b8b805e7755814fc242715c16f1dba58e1cbccb8032c2/jiter-0.14.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54b3ddf5786bc7732d293bba3411ac637ecfa200a39983166d1df86a59a43c9f", size = 380225, upload-time = "2026-04-10T14:26:10.161Z" }, - { url = "https://files.pythonhosted.org/packages/ad/5e/0ddeb7096aca099114abe36c4921016e8d251e6f35f5890240b31f1f60ae/jiter-0.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c001d5a646c2a50dc055dd526dad5d5245969e8234d2b1131d0451e81f3a373", size = 358682, upload-time = "2026-04-10T14:26:11.574Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d1/fe0c46cd7fda9cad8f1ff9ad217dc61f1e4280b21052ec6dfe88c1446ef2/jiter-0.14.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:834bb5bdabca2e91592a03d373838a8d0a1b8bbde7077ae6913fd2fc51812d00", size = 359973, upload-time = "2026-04-10T14:26:13.316Z" }, - { url = "https://files.pythonhosted.org/packages/ac/21/f5317f91729b501019184771c80d60abd89907009e7bfa6c7e348c5bdd44/jiter-0.14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4e9178be60e229b1b2b0710f61b9e24d1f4f8556985a83ff4c4f95920eea7314", size = 397568, upload-time = "2026-04-10T14:26:15.212Z" }, - { url = "https://files.pythonhosted.org/packages/e9/05/79d8f33fb2bf168db0df5c9cd16fe440a8ada57e929d3677b22712c2568f/jiter-0.14.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7e4ccff04ec03614e62c613e976a3a5860dc9714ce8266f44328bdc8b1cab2c", size = 522535, upload-time = "2026-04-10T14:26:16.956Z" }, - { url = "https://files.pythonhosted.org/packages/5c/00/d1e3ff3d2a465e67f08507d74bafb2dcd29eba91dc939820e39e8dea38b8/jiter-0.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69539d936fb5d55caf6ecd33e2e884de083ff0ea28579780d56c4403094bb8d9", size = 556709, upload-time = "2026-04-10T14:26:18.5Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/bbb2189f62ace8d95e869aa4c84c9946616f301e2d02895a6f20dcc3bba3/jiter-0.14.0-cp311-cp311-win32.whl", hash = "sha256:4927d09b3e572787cc5e0a5318601448e1ab9391bcef95677f5840c2d00eaa6d", size = 208660, upload-time = "2026-04-10T14:26:20.511Z" }, - { url = "https://files.pythonhosted.org/packages/b8/86/c500b53dcbf08575f5963e536ebd757a1f7c568272ba5d180b212c9a87fb/jiter-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:42d6ed359ac49eb922fdd565f209c57340aa06d589c84c8413e42a0f9ae1b842", size = 204659, upload-time = "2026-04-10T14:26:22.152Z" }, - { url = "https://files.pythonhosted.org/packages/75/4a/a676249049d42cb29bef82233e4fe0524d414cbe3606c7a4b311193c2f77/jiter-0.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:6dd689f5f4a5a33747b28686e051095beb214fe28cfda5e9fe58a295a788f593", size = 194772, upload-time = "2026-04-10T14:26:23.458Z" }, - { url = "https://files.pythonhosted.org/packages/5a/68/7390a418f10897da93b158f2d5a8bd0bcd73a0f9ec3bb36917085bb759ef/jiter-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2fb2ce3a7bc331256dfb14cefc34832366bb28a9aca81deaf43bbf2a5659e607", size = 316295, upload-time = "2026-04-10T14:26:24.887Z" }, - { url = "https://files.pythonhosted.org/packages/60/a0/5854ac00ff63551c52c6c89534ec6aba4b93474e7924d64e860b1c94165b/jiter-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5252a7ca23785cef5d02d4ece6077a1b556a410c591b379f82091c3001e14844", size = 315898, upload-time = "2026-04-10T14:26:26.601Z" }, - { url = "https://files.pythonhosted.org/packages/41/a1/4f44832650a16b18e8391f1bf1d6ca4909bc738351826bcc198bba4357f4/jiter-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c409578cbd77c338975670ada777add4efd53379667edf0aceea730cabede6fb", size = 343730, upload-time = "2026-04-10T14:26:28.326Z" }, - { url = "https://files.pythonhosted.org/packages/48/64/a329e9d469f86307203594b1707e11ae51c3348d03bfd514a5f997870012/jiter-0.14.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ede4331a1899d604463369c730dbb961ffdc5312bc7f16c41c2896415b1304a", size = 370102, upload-time = "2026-04-10T14:26:30.089Z" }, - { url = "https://files.pythonhosted.org/packages/94/c1/5e3dfc59635aa4d4c7bd20a820ac1d09b8ed851568356802cf1c08edb3cf/jiter-0.14.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92cd8b6025981a041f5310430310b55b25ca593972c16407af8837d3d7d2ca01", size = 461335, upload-time = "2026-04-10T14:26:31.911Z" }, - { url = "https://files.pythonhosted.org/packages/e3/1b/dd157009dbc058f7b00108f545ccb72a2d56461395c4fc7b9cfdccb00af4/jiter-0.14.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:351bf6eda4e3a7ceb876377840c702e9a3e4ecc4624dbfb2d6463c67ae52637d", size = 378536, upload-time = "2026-04-10T14:26:33.595Z" }, - { url = "https://files.pythonhosted.org/packages/91/78/256013667b7c10b8834f8e6e54cd3e562d4c6e34227a1596addccc05e38c/jiter-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1dcfbeb93d9ecd9ca128bbf8910120367777973fa193fb9a39c31237d8df165", size = 353859, upload-time = "2026-04-10T14:26:35.098Z" }, - { url = "https://files.pythonhosted.org/packages/de/d9/137d65ade9093a409fe80955ce60b12bb753722c986467aeda47faf450ad/jiter-0.14.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:ae039aaef8de3f8157ecc1fdd4d85043ac4f57538c245a0afaecb8321ec951c3", size = 357626, upload-time = "2026-04-10T14:26:36.685Z" }, - { url = "https://files.pythonhosted.org/packages/2e/48/76750835b87029342727c1a268bea8878ab988caf81ee4e7b880900eeb5a/jiter-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7d9d51eb96c82a9652933bd769fe6de66877d6eb2b2440e281f2938c51b5643e", size = 393172, upload-time = "2026-04-10T14:26:38.097Z" }, - { url = "https://files.pythonhosted.org/packages/a6/60/456c4e81d5c8045279aefe60e9e483be08793828800a4e64add8fdde7f2a/jiter-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d824ca4148b705970bf4e120924a212fdfca9859a73e42bd7889a63a4ea6bb98", size = 520300, upload-time = "2026-04-10T14:26:39.532Z" }, - { url = "https://files.pythonhosted.org/packages/a8/9f/2020e0984c235f678dced38fe4eec3058cf528e6af36ebf969b410305941/jiter-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff3a6465b3a0f54b1a430f45c3c0ba7d61ceb45cbc3e33f9e1a7f638d690baf3", size = 553059, upload-time = "2026-04-10T14:26:40.991Z" }, - { url = "https://files.pythonhosted.org/packages/ef/32/e2d298e1a22a4bbe6062136d1c7192db7dba003a6975e51d9a9eecabc4c2/jiter-0.14.0-cp312-cp312-win32.whl", hash = "sha256:5dec7c0a3e98d2a3f8a2e67382d0d7c3ac60c69103a4b271da889b4e8bb1e129", size = 206030, upload-time = "2026-04-10T14:26:42.517Z" }, - { url = "https://files.pythonhosted.org/packages/36/ac/96369141b3d8a4a8e4590e983085efe1c436f35c0cda940dd76d942e3e40/jiter-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:fc7e37b4b8bc7e80a63ad6cfa5fc11fab27dbfea4cc4ae644b1ab3f273dc348f", size = 201603, upload-time = "2026-04-10T14:26:44.328Z" }, - { url = "https://files.pythonhosted.org/packages/01/c3/75d847f264647017d7e3052bbcc8b1e24b95fa139c320c5f5066fa7a0bdd/jiter-0.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:ee4a72f12847ef29b072aee9ad5474041ab2924106bdca9fcf5d7d965853e057", size = 191525, upload-time = "2026-04-10T14:26:46Z" }, - { url = "https://files.pythonhosted.org/packages/97/2a/09f70020898507a89279659a1afe3364d57fc1b2c89949081975d135f6f5/jiter-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:af72f204cf4d44258e5b4c1745130ac45ddab0e71a06333b01de660ab4187a94", size = 315502, upload-time = "2026-04-10T14:26:47.697Z" }, - { url = "https://files.pythonhosted.org/packages/d6/be/080c96a45cd74f9fce5db4fd68510b88087fb37ffe2541ff73c12db92535/jiter-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4b77da71f6e819be5fbcec11a453fde5b1d0267ef6ed487e2a392fd8e14e4e3a", size = 314870, upload-time = "2026-04-10T14:26:49.149Z" }, - { url = "https://files.pythonhosted.org/packages/7d/5e/2d0fee155826a968a832cc32438de5e2a193292c8721ca70d0b53e58245b/jiter-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f4ea612fe8b84b8b04e51d0e78029ecf3466348e25973f953de6e6a59aa4c1", size = 343406, upload-time = "2026-04-10T14:26:50.762Z" }, - { url = "https://files.pythonhosted.org/packages/70/af/bf9ee0d3a4f8dc0d679fc1337f874fe60cdbf841ebbb304b374e1c9aaceb/jiter-0.14.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62fe2451f8fcc0240261e6a4df18ecbcd58327857e61e625b2393ea3b468aac9", size = 369415, upload-time = "2026-04-10T14:26:52.188Z" }, - { url = "https://files.pythonhosted.org/packages/0f/83/8e8561eadba31f4d3948a5b712fb0447ec71c3560b57a855449e7b8ddc98/jiter-0.14.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6112f26f5afc75bcb475787d29da3aa92f9d09c7858f632f4be6ffe607be82e9", size = 461456, upload-time = "2026-04-10T14:26:53.611Z" }, - { url = "https://files.pythonhosted.org/packages/f6/c9/c5299e826a5fe6108d172b344033f61c69b1bb979dd8d9ddd4278a160971/jiter-0.14.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:215a6cb8fb7dc702aa35d475cc00ddc7f970e5c0b1417fb4b4ac5d82fa2a29db", size = 378488, upload-time = "2026-04-10T14:26:55.211Z" }, - { url = "https://files.pythonhosted.org/packages/5d/37/c16d9d15c0a471b8644b1abe3c82668092a707d9bedcf076f24ff2e380cd/jiter-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ab96a30fb3cb2c7e0cd33f7616c8860da5f5674438988a54ac717caccdbaa", size = 353242, upload-time = "2026-04-10T14:26:56.705Z" }, - { url = "https://files.pythonhosted.org/packages/58/ea/8050cb0dc654e728e1bfacbc0c640772f2181af5dedd13ae70145743a439/jiter-0.14.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:3a99c1387b1f2928f799a9de899193484d66206a50e98233b6b088a7f0c1edb2", size = 356823, upload-time = "2026-04-10T14:26:58.281Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/cf71506d270e5f84d97326bf220e47aed9b95e9a4a060758fb07772170ab/jiter-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ab18d11074485438695f8d34a1b6da61db9754248f96d51341956607a8f39985", size = 392564, upload-time = "2026-04-10T14:27:00.018Z" }, - { url = "https://files.pythonhosted.org/packages/b0/cc/8c6c74a3efb5bd671bfd14f51e8a73375464ca914b1551bc3b40e26ac2c9/jiter-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:801028dcfc26ac0895e4964cbc0fd62c73be9fd4a7d7b1aaf6e5790033a719b7", size = 520322, upload-time = "2026-04-10T14:27:01.664Z" }, - { url = "https://files.pythonhosted.org/packages/41/24/68d7b883ec959884ddf00d019b2e0e82ba81b167e1253684fa90519ce33c/jiter-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ad425b087aafb4a1c7e1e98a279200743b9aaf30c3e0ba723aec93f061bd9bc8", size = 552619, upload-time = "2026-04-10T14:27:03.316Z" }, - { url = "https://files.pythonhosted.org/packages/b6/89/b1a0985223bbf3150ff9e8f46f98fc9360c1de94f48abe271bbe1b465682/jiter-0.14.0-cp313-cp313-win32.whl", hash = "sha256:882bcb9b334318e233950b8be366fe5f92c86b66a7e449e76975dfd6d776a01f", size = 205699, upload-time = "2026-04-10T14:27:04.662Z" }, - { url = "https://files.pythonhosted.org/packages/4c/19/3f339a5a7f14a11730e67f6be34f9d5105751d547b615ef593fa122a5ded/jiter-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:9b8c571a5dba09b98bd3462b5a53f27209a5cbbe85670391692ede71974e979f", size = 201323, upload-time = "2026-04-10T14:27:06.139Z" }, - { url = "https://files.pythonhosted.org/packages/50/56/752dd89c84be0e022a8ea3720bcfa0a8431db79a962578544812ce061739/jiter-0.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:34f19dcc35cb1abe7c369b3756babf8c7f04595c0807a848df8f26ef8298ef92", size = 191099, upload-time = "2026-04-10T14:27:07.564Z" }, - { url = "https://files.pythonhosted.org/packages/91/28/292916f354f25a1fe8cf2c918d1415c699a4a659ae00be0430e1c5d9ffea/jiter-0.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e89bcd7d426a75bb4952c696b267075790d854a07aad4c9894551a82c5b574ab", size = 320880, upload-time = "2026-04-10T14:27:09.326Z" }, - { url = "https://files.pythonhosted.org/packages/ad/c7/b002a7d8b8957ac3d469bd59c18ef4b1595a5216ae0de639a287b9816023/jiter-0.14.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b25beaa0d4447ea8c7ae0c18c688905d34840d7d0b937f2f7bdd52162c98a40", size = 346563, upload-time = "2026-04-10T14:27:11.287Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3b/f8d07580d8706021d255a6356b8fab13ee4c869412995550ce6ed4ddf97d/jiter-0.14.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:651a8758dd413c51e3b7f6557cdc6921faf70b14106f45f969f091f5cda990ea", size = 357928, upload-time = "2026-04-10T14:27:12.729Z" }, - { url = "https://files.pythonhosted.org/packages/47/5b/ac1a974da29e35507230383110ffec59998b290a8732585d04e19a9eb5ba/jiter-0.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e1a7eead856a5038a8d291f1447176ab0b525c77a279a058121b5fccee257f6f", size = 203519, upload-time = "2026-04-10T14:27:14.125Z" }, - { url = "https://files.pythonhosted.org/packages/96/6d/9fc8433d667d2454271378a79747d8c76c10b51b482b454e6190e511f244/jiter-0.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e692633a12cda97e352fdcd1c4acc971b1c28707e1e33aeef782b0cbf051975", size = 190113, upload-time = "2026-04-10T14:27:16.638Z" }, - { url = "https://files.pythonhosted.org/packages/4f/1e/354ed92461b165bd581f9ef5150971a572c873ec3b68a916d5aa91da3cc2/jiter-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:6f396837fc7577871ca8c12edaf239ed9ccef3bbe39904ae9b8b63ce0a48b140", size = 315277, upload-time = "2026-04-10T14:27:18.109Z" }, - { url = "https://files.pythonhosted.org/packages/a6/95/8c7c7028aa8636ac21b7a55faef3e34215e6ed0cbf5ae58258427f621aa3/jiter-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a4d50ea3d8ba4176f79754333bd35f1bbcd28e91adc13eb9b7ca91bc52a6cef9", size = 315923, upload-time = "2026-04-10T14:27:19.603Z" }, - { url = "https://files.pythonhosted.org/packages/47/40/e2a852a44c4a089f2681a16611b7ce113224a80fd8504c46d78491b47220/jiter-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce17f8a050447d1b4153bda4fb7d26e6a9e74eb4f4a41913f30934c5075bf615", size = 344943, upload-time = "2026-04-10T14:27:21.262Z" }, - { url = "https://files.pythonhosted.org/packages/fc/1f/670f92adee1e9895eac41e8a4d623b6da68c4d46249d8b556b60b63f949e/jiter-0.14.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4f1c4b125e1652aefbc2e2c1617b60a160ab789d180e3d423c41439e5f32850", size = 369725, upload-time = "2026-04-10T14:27:22.766Z" }, - { url = "https://files.pythonhosted.org/packages/01/2f/541c9ba567d05de1c4874a0f8f8c5e3fd78e2b874266623da9a775cf46e0/jiter-0.14.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be808176a6a3a14321d18c603f2d40741858a7c4fc982f83232842689fe86dd9", size = 461210, upload-time = "2026-04-10T14:27:24.315Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a9/c31cbec09627e0d5de7aeaec7690dba03e090caa808fefd8133137cf45bc/jiter-0.14.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26679d58ba816f88c3849306dd58cb863a90a1cf352cdd4ef67e30ccf8a77994", size = 380002, upload-time = "2026-04-10T14:27:26.155Z" }, - { url = "https://files.pythonhosted.org/packages/50/02/3c05c1666c41904a2f607475a73e7a4763d1cbde2d18229c4f85b22dc253/jiter-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80381f5a19af8fa9aef743f080e34f6b25ebd89656475f8cf0470ec6157052aa", size = 354678, upload-time = "2026-04-10T14:27:27.701Z" }, - { url = "https://files.pythonhosted.org/packages/7d/97/e15b33545c2b13518f560d695f974b9891b311641bdcf178d63177e8801e/jiter-0.14.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:004df5fdb8ecbd6d99f3227df18ba1a259254c4359736a2e6f036c944e02d7c5", size = 358920, upload-time = "2026-04-10T14:27:29.256Z" }, - { url = "https://files.pythonhosted.org/packages/ad/d2/8b1461def6b96ba44530df20d07ef7a1c7da22f3f9bf1727e2d611077bf1/jiter-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cff5708f7ed0fa098f2b53446c6fa74c48469118e5cd7497b4f1cd569ab06928", size = 394512, upload-time = "2026-04-10T14:27:31.344Z" }, - { url = "https://files.pythonhosted.org/packages/e3/88/837566dd6ed6e452e8d3205355afd484ce44b2533edfa4ed73a298ea893e/jiter-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:2492e5f06c36a976d25c7cc347a60e26d5470178d44cde1b9b75e60b4e519f28", size = 521120, upload-time = "2026-04-10T14:27:33.299Z" }, - { url = "https://files.pythonhosted.org/packages/89/6b/b00b45c4d1b4c031777fe161d620b755b5b02cdade1e316dcb46e4471d63/jiter-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7609cfbe3a03d37bfdbf5052012d5a879e72b83168a363deae7b3a26564d57de", size = 553668, upload-time = "2026-04-10T14:27:34.868Z" }, - { url = "https://files.pythonhosted.org/packages/ad/d8/6fe5b42011d19397433d345716eac16728ac241862a2aac9c91923c7509a/jiter-0.14.0-cp314-cp314-win32.whl", hash = "sha256:7282342d32e357543565286b6450378c3cd402eea333fc1ebe146f1fabb306fc", size = 207001, upload-time = "2026-04-10T14:27:36.455Z" }, - { url = "https://files.pythonhosted.org/packages/e5/43/5c2e08da1efad5e410f0eaaabeadd954812612c33fbbd8fd5328b489139d/jiter-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:bd77945f38866a448e73b0b7637366afa814d4617790ecd88a18ca74377e6c02", size = 202187, upload-time = "2026-04-10T14:27:38Z" }, - { url = "https://files.pythonhosted.org/packages/aa/1f/6e39ac0b4cdfa23e606af5b245df5f9adaa76f35e0c5096790da430ca506/jiter-0.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:f2d4c61da0821ee42e0cdf5489da60a6d074306313a377c2b35af464955a3611", size = 192257, upload-time = "2026-04-10T14:27:39.504Z" }, - { url = "https://files.pythonhosted.org/packages/05/57/7dbc0ffbbb5176a27e3518716608aa464aee2e2887dc938f0b900a120449/jiter-0.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bf7ff85517dd2f20a5750081d2b75083c1b269cf75afc7511bdf1f9548beb3b", size = 323441, upload-time = "2026-04-10T14:27:41.039Z" }, - { url = "https://files.pythonhosted.org/packages/83/6e/7b3314398d8983f06b557aa21b670511ec72d3b79a68ee5e4d9bff972286/jiter-0.14.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8ef8791c3e78d6c6b157c6d360fbb5c715bebb8113bc6a9303c5caff012754a", size = 348109, upload-time = "2026-04-10T14:27:42.552Z" }, - { url = "https://files.pythonhosted.org/packages/ae/4f/8dc674bcd7db6dba566de73c08c763c337058baff1dbeb34567045b27cdc/jiter-0.14.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e74663b8b10da1fe0f4e4703fd7980d24ad17174b6bb35d8498d6e3ebce2ae6a", size = 368328, upload-time = "2026-04-10T14:27:44.574Z" }, - { url = "https://files.pythonhosted.org/packages/3b/5f/188e09a1f20906f98bbdec44ed820e19f4e8eb8aff88b9d1a5a497587ff3/jiter-0.14.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1aca29ba52913f78362ec9c2da62f22cdc4c3083313403f90c15460979b84d9b", size = 463301, upload-time = "2026-04-10T14:27:46.717Z" }, - { url = "https://files.pythonhosted.org/packages/ac/f0/19046ef965ed8f349e8554775bb12ff4352f443fbe12b95d31f575891256/jiter-0.14.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b39b7d87a952b79949af5fef44d2544e58c21a28da7f1bae3ef166455c61746", size = 378891, upload-time = "2026-04-10T14:27:48.32Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c3/da43bd8431ee175695777ee78cf0e93eacbb47393ff493f18c45231b427d/jiter-0.14.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d918a68b26e9fab068c2b5453577ef04943ab2807b9a6275df2a812599a310", size = 360749, upload-time = "2026-04-10T14:27:49.88Z" }, - { url = "https://files.pythonhosted.org/packages/72/26/e054771be889707c6161dbdec9c23d33a9ec70945395d70f07cfea1e9a6f/jiter-0.14.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:b08997c35aee1201c1a5361466a8fb9162d03ae7bf6568df70b6c859f1e654a4", size = 358526, upload-time = "2026-04-10T14:27:51.504Z" }, - { url = "https://files.pythonhosted.org/packages/c3/0f/7bea65ea2a6d91f2bf989ff11a18136644392bf2b0497a1fa50934c30a9c/jiter-0.14.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:260bf7ca20704d58d41f669e5e9fe7fe2fa72901a6b324e79056f5d52e9c9be2", size = 393926, upload-time = "2026-04-10T14:27:53.368Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a1/b1ff7d70deef61ac0b7c6c2f12d2ace950cdeecb4fdc94500a0926802857/jiter-0.14.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:37826e3df29e60f30a382f9294348d0238ef127f4b5d7f5f8da78b5b9e050560", size = 521052, upload-time = "2026-04-10T14:27:55.058Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7b/3b0649983cbaf15eda26a414b5b1982e910c67bd6f7b1b490f3cfc76896a/jiter-0.14.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:645be49c46f2900937ba0eaf871ad5183c96858c0af74b6becc7f4e367e36e06", size = 553716, upload-time = "2026-04-10T14:27:57.269Z" }, - { url = "https://files.pythonhosted.org/packages/97/f8/33d78c83bd93ae0c0af05293a6660f88a1977caef39a6d72a84afab94ce0/jiter-0.14.0-cp314-cp314t-win32.whl", hash = "sha256:2f7877ed45118de283786178eceaf877110abacd04fde31efff3940ae9672674", size = 207957, upload-time = "2026-04-10T14:27:59.285Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ac/2b760516c03e2227826d1f7025d89bf6bf6357a28fe75c2a2800873c50bf/jiter-0.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:14c0cb10337c49f5eafe8e7364daca5e29a020ea03580b8f8e6c597fed4e1588", size = 204690, upload-time = "2026-04-10T14:28:00.962Z" }, - { url = "https://files.pythonhosted.org/packages/dc/2e/a44c20c58aeed0355f2d326969a181696aeb551a25195f47563908a815be/jiter-0.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5419d4aa2024961da9fe12a9cfe7484996735dca99e8e090b5c88595ef1951ff", size = 191338, upload-time = "2026-04-10T14:28:02.853Z" }, - { url = "https://files.pythonhosted.org/packages/32/a1/ef34ca2cab2962598591636a1804b93645821201cc0095d4a93a9a329c9d/jiter-0.14.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a25ffa2dbbdf8721855612f6dca15c108224b12d0c4024d0ac3d7902132b4211", size = 311366, upload-time = "2026-04-10T14:28:27.943Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/520576a532a6b8a6f42747afed289c8448c879a34d7802fe2c832d4fd38f/jiter-0.14.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ac9cbaa86c10996b92bd12c91659b60f939f8e28fcfa6bc11a0e90a774ce95b", size = 309873, upload-time = "2026-04-10T14:28:29.688Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7c/c16db114ea1f2f532f198aa8dc39585026af45af362c69a0492f31bc4821/jiter-0.14.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:844e73b6c56b505e9e169234ea3bdea2ea43f769f847f47ac559ba1d2361ebea", size = 344816, upload-time = "2026-04-10T14:28:31.348Z" }, - { url = "https://files.pythonhosted.org/packages/99/8f/15e7741ff19e9bcd4d753f7ff22f988fd54592f134ca13701c13ea8c20e0/jiter-0.14.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52c076f187405fc21523c746c04399c9af8ece566077ed147b2126f2bcba577", size = 351445, upload-time = "2026-04-10T14:28:33.093Z" }, - { url = "https://files.pythonhosted.org/packages/21/42/9042c3f3019de4adcb8c16591c325ec7255beea9fcd33a42a43f3b0b1000/jiter-0.14.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:fbd9e482663ca9d005d051330e4d2d8150bb208a209409c10f7e7dfdf7c49da9", size = 308810, upload-time = "2026-04-10T14:28:34.673Z" }, - { url = "https://files.pythonhosted.org/packages/60/cf/a7e19b308bd86bb04776803b1f01a5f9a287a4c55205f4708827ee487fbf/jiter-0.14.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:33a20d838b91ef376b3a56896d5b04e725c7df5bc4864cc6569cf046a8d73b6d", size = 308443, upload-time = "2026-04-10T14:28:36.658Z" }, - { url = "https://files.pythonhosted.org/packages/ca/44/e26ede3f0caeff93f222559cb0cc4ca68579f07d009d7b6010c5b586f9b1/jiter-0.14.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:432c4db5255d86a259efde91e55cb4c8d18c0521d844c9e2e7efcce3899fb016", size = 343039, upload-time = "2026-04-10T14:28:38.356Z" }, - { url = "https://files.pythonhosted.org/packages/da/e9/1f9ada30cef7b05e74bb06f52127e7a724976c225f46adb65c37b1dadfb6/jiter-0.14.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f00d94b281174144d6532a04b66a12cb866cbdc47c3af3bfe2973677f9861a", size = 349613, upload-time = "2026-04-10T14:28:40.066Z" }, +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/b5/55f06bb281d92fb3cc86d14e1def2bd908bb77693183e7cb1f5a3c388b0c/jiter-0.15.0.tar.gz", hash = "sha256:4251acc80e2b7c9b7b8823456ea0fceeb0734dac2df7636d3c711b38476b5a76", size = 166640, upload-time = "2026-05-19T10:09:48.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/13/daa722f5765c393576f466378f9dfd29d77c9bed939e0688f96afa3601ea/jiter-0.15.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0f862193b8696249d22ec433e85fd2ab0ad9596bc3e45e6c0bc55e8aeba97be2", size = 310899, upload-time = "2026-05-19T10:07:12.89Z" }, + { url = "https://files.pythonhosted.org/packages/7f/82/2d2551829b082f4b6d82b9f939b031fb808a10aab1ec0664f82e150bb9a2/jiter-0.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1303d4d68a9b051ea90502402063ecf3807da00ad2affa19ca1ae3b90b3c5f67", size = 314963, upload-time = "2026-05-19T10:07:14.539Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0a/8b1a51466f7fe9f31dbe4bc7e0ca848674f9825e0f737b929b97e8c60aa7/jiter-0.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:392b8ab019e5502d08aff85c6272209c24bc2cbe706ea82a56368f524236614a", size = 341730, upload-time = "2026-05-19T10:07:15.869Z" }, + { url = "https://files.pythonhosted.org/packages/f6/2a/e71dea19822e2e404e83992a08c1d6b9b617bb944f28c9c2fbd85d02c91e/jiter-0.15.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:773b6eb282ce11ee19f05f6b2d4404fa308e5bbd353b0b80a0262caad6db2cd7", size = 366214, upload-time = "2026-05-19T10:07:17.259Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/97e1fa539d124a509a00ab7f669289d1c1d236ecabf12948a18f16c91082/jiter-0.15.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2c0c44d569ce0f2850f5c926f8caeb5f245fbc84475aeb36efccc2103e6dbd", size = 459527, upload-time = "2026-05-19T10:07:18.741Z" }, + { url = "https://files.pythonhosted.org/packages/d1/7a/4a68d331aef8cf2e2393c14a3aacb635c62aa86071b0229899fb5baaa907/jiter-0.15.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:032396229564bca02440396bd327710719f724f5e7b7e9f7a8eb3faa4a2c2281", size = 375451, upload-time = "2026-05-19T10:07:20.208Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/1c445c2b6f0e30a274dc8082e0c3c7825411cce80d726bccd697c98cc8d3/jiter-0.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3d37768fce7f88dd2a8c6091f2325dea27d30d30d5c6e7a1c0f0af77723b708", size = 349428, upload-time = "2026-05-19T10:07:22.372Z" }, + { url = "https://files.pythonhosted.org/packages/00/94/e20d38984fc17a636371bffd2ae0f698124fdc8e75ef969cd2da6ba7cea7/jiter-0.15.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:2c9cb907439d20bd0c7d7565ca01ee52234203208433749bae5b516907526928", size = 355405, upload-time = "2026-05-19T10:07:23.916Z" }, + { url = "https://files.pythonhosted.org/packages/94/fa/4d09f814779d0ea80a28ed8e4c6662ec9a4a8ecef0ac52190ebac6262d14/jiter-0.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9100ddbec09741cc66feb0fc6773f8bdbd0e3c345689368f260082ff85dcc0cd", size = 393688, upload-time = "2026-05-19T10:07:25.854Z" }, + { url = "https://files.pythonhosted.org/packages/54/9d/8eb5d4fb8bf7e93a75964a5da71a75c67c864baf7fa3f98598187b3c7e57/jiter-0.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ae1b0d82ac2d987f9ea512b1c9adfcc71a28de3dea3a6039b54d76cffda9901e", size = 520853, upload-time = "2026-05-19T10:07:27.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2c/5e07874e59e623a943a0acf1552a80d05b70f31b402287a8fc6d7ec634c7/jiter-0.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8020c99ec13a7db2b6f96cbe82ef4721c88b426a4892f27478044af0284615ef", size = 551016, upload-time = "2026-05-19T10:07:28.846Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/d2d34422143474cadc15b60d482b1c35683dbc5c63c24346ddd0df09bcaf/jiter-0.15.0-cp311-cp311-win32.whl", hash = "sha256:42bfb257930800cf43e7c62c832402c704ab60797c992faf88d20e903eac8f32", size = 209518, upload-time = "2026-05-19T10:07:30.431Z" }, + { url = "https://files.pythonhosted.org/packages/1d/7d/52778b930e5cc3e52a37d950b1c10494244308b4329b25a0ff0d88303a81/jiter-0.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:860a74063284a2ae9bfedd694f299cc2c68e2696c5f3d440cc9d18bb81b9dd04", size = 200565, upload-time = "2026-05-19T10:07:32.125Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4f/d9b4067feb69b3fa6eb0488e1b59e2ad5b463fe39f59e527eab2aca00bb0/jiter-0.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:37a10c377ce3a4a85f4a67f28b7afe093154cde77eaf248a72e856aa08b4d865", size = 195488, upload-time = "2026-05-19T10:07:33.846Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/4f6bddbcde3c71e56d0aa1337ec95950f3d27dd4153e25aadf0feac71751/jiter-0.15.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0e90a1c315a0226ec822d973817967f9223b7701546c8c2a7913e7ab0926294d", size = 308793, upload-time = "2026-05-19T10:07:35.25Z" }, + { url = "https://files.pythonhosted.org/packages/01/84/c01099b59a285a1ebba64ae93f62bfa036675340fd1b0045ae65890a0442/jiter-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8c9004af7c8d67cce7f1aae1026fb55607f4aa600710d08ede3a3ce4aeefe7e0", size = 309570, upload-time = "2026-05-19T10:07:36.919Z" }, + { url = "https://files.pythonhosted.org/packages/58/64/8fb7f9d45bb98190355454cd04dad8d8f27223d6bd52f83af07f637168a6/jiter-0.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c210f8b35dc6f30aafd4b4365ca89b9d1189f21ab49b8e68fa6322a847aef138", size = 336783, upload-time = "2026-05-19T10:07:38.694Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b6/f5739011d009b3a30f6a53c5240979030ba29ae46a8c67e3a15759f7c37d/jiter-0.15.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f30bae8bc1c2d613e28e5af3e8cceb09b742f1c8a8a5f839fb67afaffc03b61", size = 363555, upload-time = "2026-05-19T10:07:40.832Z" }, + { url = "https://files.pythonhosted.org/packages/e5/12/98a9d9f766665e8a3b6252454e17cb0c464606a28cf2fa09399b003345fa/jiter-0.15.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c60e71b6d10cfc284c9bf36bd885e8d44c46f688ce50aa91b5edd90181dea687", size = 452255, upload-time = "2026-05-19T10:07:42.62Z" }, + { url = "https://files.pythonhosted.org/packages/e8/d5/60f972840f79c5e7544fce567c56f1e4e50468f996baba3e78d823dd62a6/jiter-0.15.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ab068bce62a45aa3e7367eceaffb5dde60b7eb853be8dece45132e3d0ff4879", size = 373559, upload-time = "2026-05-19T10:07:44.201Z" }, + { url = "https://files.pythonhosted.org/packages/ee/cf/d46ef1234ba335aabc2f013210db8e0821a22f5e644a2e9449df199ecc23/jiter-0.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa248c9eb220197d363f688818dac2fd4b2f0cd7d843ca7105d652034823427d", size = 346055, upload-time = "2026-05-19T10:07:46.005Z" }, + { url = "https://files.pythonhosted.org/packages/f0/63/4d2749d8d54d230bad9b3a6b0d00cc28c6ff6b2fdffc26a8ccf76cc5a974/jiter-0.15.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2a77aadd57cac1682e4401a72724d2796d89a4ba129b1a5812aa94ee480826eb", size = 351406, upload-time = "2026-05-19T10:07:47.855Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b9/9965b990035d8773328e0a8c8b457a87bf2b19f6c4126d9d99296be5d16a/jiter-0.15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ae901f3a55bfafdde31d289590fa25e3245735a2b1e8c7cc15871710a002871", size = 389357, upload-time = "2026-05-19T10:07:49.665Z" }, + { url = "https://files.pythonhosted.org/packages/2d/55/9ddf903deda1413e87fed792f416b7123daee5b8efbad6a202a7421c36a5/jiter-0.15.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f0b271b462769543716f92d3a4f90527df6ef5ed05ee95ec4137f513e21e1b77", size = 517263, upload-time = "2026-05-19T10:07:51.537Z" }, + { url = "https://files.pythonhosted.org/packages/e8/76/a0c40ad064d3a20a4fde231e35d56e9a01ce82164278180e82d5daf85469/jiter-0.15.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2fb6a5d26af81fc0f00f9360a891e05cf755e149bba391c4d563adc54812973d", size = 548646, upload-time = "2026-05-19T10:07:53.196Z" }, + { url = "https://files.pythonhosted.org/packages/23/4f/eca9b954942916ba2f453891b8593ab444cd872396fe66a3936616f236f3/jiter-0.15.0-cp312-cp312-win32.whl", hash = "sha256:c2f6bb8b5216ab9e7873bc08b5d7bef2b8abbb578a3069bf1cd14a45d71d771d", size = 206427, upload-time = "2026-05-19T10:07:55.307Z" }, + { url = "https://files.pythonhosted.org/packages/95/bf/8ead82a87495149542748e828d153fd232a512a22c83b02c4815c1a9c7d8/jiter-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:40b2c7e92c44a84d748d21706c68dc6ff8161d80b59c99d774721a0d2317d7c7", size = 197300, upload-time = "2026-05-19T10:07:56.651Z" }, + { url = "https://files.pythonhosted.org/packages/f4/e4/9b8a78fb2d894471bc344e37f1949bdd784bd914d031dba0ba3a40c71dd7/jiter-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:cc0bc345cf2df9d1c00ac443f50d543c1ccfa8b0422cb85b1ab70d681c0b255b", size = 192702, upload-time = "2026-05-19T10:07:58.307Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f4/f708c900ecee41b2025ef8413d5351e5649eb2125c506f6720cc69b06f5c/jiter-0.15.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1c11465f97e2abf45a014b83b730222f8f1c5335e802c7055a67d50de6f1f4e3", size = 307829, upload-time = "2026-05-19T10:07:59.704Z" }, + { url = "https://files.pythonhosted.org/packages/86/59/db537c0949e83668c38481d426b9f2fd5ab758c4ee53a811dd0a510626a0/jiter-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e7b1776f0797956c509e123d0952d10d293a9492dea9f288ab9570ec01d1a5", size = 308445, upload-time = "2026-05-19T10:08:01.184Z" }, + { url = "https://files.pythonhosted.org/packages/37/38/ea0e13b18c30ef951da0d47d39e7fa9edb82a93a62990ffbd7cea9b622d4/jiter-0.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351a341c2105aa430b7047e30f1bf7975f6313b00165d3fc07be2edaf741f279", size = 336181, upload-time = "2026-05-19T10:08:02.688Z" }, + { url = "https://files.pythonhosted.org/packages/58/fc/2303901b16c4ba05865588990a420c0b4156270b44379c20931544a1d962/jiter-0.15.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ab395feec8d249ec4044e228e98a7033f043426a265df439dc3698823f0a4e4", size = 362985, upload-time = "2026-05-19T10:08:04.394Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6f/11bace093c52e7d4d26c8e606ccd7ae8c972189622469ec0d9e28161e28b/jiter-0.15.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2a438005b6f22d0273413484d6094d7c2c5d10ec1b3a3bf128e0d1d3ba53258", size = 453292, upload-time = "2026-05-19T10:08:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/22/db/987f2f086ca4d7a6582eb4ccd513f9b26b42d9e4243a087609a3137a8fc7/jiter-0.15.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f18f85e4218d1b40f000f42a92239a7a61a902cd42c65e6c360dbd17dcb20894", size = 373501, upload-time = "2026-05-19T10:08:07.857Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/89fbcabb2739b7a5b8dc959a1b6c5761f6484f5fed3486854b3c789bb1de/jiter-0.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1aa62e277fc1cbd80e6deacae6f4d983b41b3d7728e0645c5d741a6149bba45", size = 344683, upload-time = "2026-05-19T10:08:09.431Z" }, + { url = "https://files.pythonhosted.org/packages/30/6f/6cca7692e7dddfec6d8d76c54dc97f2af2a41df4ac0674b999df1f09a5f3/jiter-0.15.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:6550fa135c7deb8ead6af49ed7ff648532ea8334a1447fe34a36315ef79c5c29", size = 350892, upload-time = "2026-05-19T10:08:11.352Z" }, + { url = "https://files.pythonhosted.org/packages/39/14/0338d6190cb8e6d22e677ab1d4eabd4117f67cca70c54cd04b82ff64e068/jiter-0.15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:066f8f33f18b2419cd8213b2436fa7fbc9c499f315971cfa3ce1f9820c001b1b", size = 388723, upload-time = "2026-05-19T10:08:12.912Z" }, + { url = "https://files.pythonhosted.org/packages/90/31/cc19f4a1bdb6afb09ce6a2f2615aa8d44d994eba0d8e6105ed1af920e736/jiter-0.15.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:75e8a04e91432dde9f1838373cf93d23726c79d3e908d319acf0e796f85592e7", size = 516648, upload-time = "2026-05-19T10:08:14.808Z" }, + { url = "https://files.pythonhosted.org/packages/49/9f/833c541512cd091b63c10c0381973dfe11bc7a503a818c16384417e0c81e/jiter-0.15.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a97261f1fccb8e50ecd2890a96e46efdc3f57c80a197324c6777827231eca712", size = 547382, upload-time = "2026-05-19T10:08:16.927Z" }, + { url = "https://files.pythonhosted.org/packages/d2/11/e7b70e91f90bc4477e8eee9e8a5f7cf3cb41b4525d6394dc98a714eb8f7f/jiter-0.15.0-cp313-cp313-win32.whl", hash = "sha256:c77496cb10bd7549690fbbab3e5ec05857b83e49276f4a9423a766ddd2afcd4c", size = 205845, upload-time = "2026-05-19T10:08:18.401Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/5c20d9ad6f02c493e4023e5d2d09e1c1f15fe2753c9102c544aff068a88e/jiter-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b15741f501469009ae0ae90b7147958a664a7dede40aa7ff174a8a4645f546d0", size = 196842, upload-time = "2026-05-19T10:08:20.131Z" }, + { url = "https://files.pythonhosted.org/packages/6b/11/1eb400ef248e8c925fd883fbe325daf5e42cd1b0d308539dd332bd4f7ffc/jiter-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d6a60072b44c3c2b797a7ddcbcbbf2b34ea3cfd4721580fbfd2a09d9d9b84ba", size = 192212, upload-time = "2026-05-19T10:08:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/8a/60/2fd8d7c79da8acf9b7b277c7616847773779356b92acfc9bb158452174da/jiter-0.15.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ef1fd24d9413f6209e00d3d5a453e67acfe004a25cc6c8e8484faed4311ab9e8", size = 315065, upload-time = "2026-05-19T10:08:23.218Z" }, + { url = "https://files.pythonhosted.org/packages/46/f4/008fb7d65e8ac2abf00811651a661e025c4ba80bbc6f378450384ddd3aed/jiter-0.15.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:144f8e72cb53dab146347b91cceac01f5481237f2b93b4a339a1ee8f8878b67c", size = 339444, upload-time = "2026-05-19T10:08:24.701Z" }, + { url = "https://files.pythonhosted.org/packages/00/55/90b0c7b9c6896c0f2a591dd36d36b71d22e09674bfef178fa03ba3f81499/jiter-0.15.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553fcac2ef2cb990877f9fc0833b8b629a3e6a5670b6b5fd58219b41a653ddc4", size = 347779, upload-time = "2026-05-19T10:08:26.408Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/69666cec5000fd57734c118437394516c749ae8dbeea9fb66d6fef9c4775/jiter-0.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:774f93f65031856bf14ad9f59bdcab8b8cad501e5ceabd51ba3525f76937a25b", size = 200395, upload-time = "2026-05-19T10:08:28.055Z" }, + { url = "https://files.pythonhosted.org/packages/39/04/a6aa62cd27e8149b0d28df5561f10f6cceaf7935a9ccf3f1c5a05f9a0cd8/jiter-0.15.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f1e1754960f38ec40613a07e5e372df67acb3b890fb383b6fb3de3e49ddbf3c7", size = 190516, upload-time = "2026-05-19T10:08:29.35Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d2/079f350ebf7859d081de30aa890f9e3be68516f754f3ba32366ffff4dcee/jiter-0.15.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:ac0d9ddea4350974be7a221fc25895f251a8fee748c889bdced2141c0fec1a49", size = 308884, upload-time = "2026-05-19T10:08:31.667Z" }, + { url = "https://files.pythonhosted.org/packages/04/4e/a2c30a7f69b48c03b20935d647479106fe932f6e63f75faf53937197e05d/jiter-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01a8222cf05ab1128e239421156c207949808acaaea2bdfd33130ae666786e86", size = 310028, upload-time = "2026-05-19T10:08:33.304Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/2e7cdfd3cf8ca967be38c48f5cf474d79f089efaf559a40f15984a77ae69/jiter-0.15.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:182226cbc930c9fab81bc2e41a4da672f89539906dadb05e75670ac07b94f71f", size = 337485, upload-time = "2026-05-19T10:08:35.259Z" }, + { url = "https://files.pythonhosted.org/packages/9b/11/15a1aa28b120b8ee5b4f1fb894c125046225f09847738bd64233d3b84883/jiter-0.15.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:71683c38c825452999b5717fcae07ea708e8c93003e808be4319c1b02e3d176e", size = 364223, upload-time = "2026-05-19T10:08:36.694Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/f442e8af5f3d0dcf47b39e83a0efd9ee45ea946aa6d04625dc3181eae3b6/jiter-0.15.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f2218e6a9e5c18bc10fe6d41ac189c442c88eacf11bad9f28ef95a9bef00e6", size = 456387, upload-time = "2026-05-19T10:08:38.143Z" }, + { url = "https://files.pythonhosted.org/packages/da/f4/37f2d2c9f64f49af7da652ed7532bb5a2372e588e6927c3fdd76f911db65/jiter-0.15.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5157de9f76eb4bc5ea74a1219366a25f945ad305641d74e04f59c54087091aa9", size = 374461, upload-time = "2026-05-19T10:08:39.869Z" }, + { url = "https://files.pythonhosted.org/packages/60/28/edcfbbbf0cb15436f36664a8908a0df47ab9006298d4cd937dc08ea932d6/jiter-0.15.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c5db5527c221249a876160663ab891ace358c17f7b9c93ec1478b7f0550e5c", size = 345924, upload-time = "2026-05-19T10:08:41.668Z" }, + { url = "https://files.pythonhosted.org/packages/47/13/89fba6398dab7f202b7278c4b4aac122399d2c0183971c4a57a3b7088df5/jiter-0.15.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:3e4540b8e74e4268811ac05db226a6a128ff572e7e0ce3f1163b693cadb184cd", size = 352283, upload-time = "2026-05-19T10:08:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/1b/da/0f6af8cef2c565a1ab44d970f268c43ccaa72707386ea6388e6fe2b6cd26/jiter-0.15.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:62ebd14e47e9aed9df4472afcb2663668ce4d74891cd54f86bf6e44029d6dc89", size = 389985, upload-time = "2026-05-19T10:08:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ec/b9cb7d6d29e24ee14910266157d2a279d7a8f60ee0df7fa840882976ba64/jiter-0.15.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0be6f5ad41a809f303f416d17cec92a7a725902fb9b4f3de3d19362ac0ef8554", size = 517695, upload-time = "2026-05-19T10:08:46.486Z" }, + { url = "https://files.pythonhosted.org/packages/64/5e/6d1bda880723aae0ad86b4b763f044362448efe31e3e819635d41cb03451/jiter-0.15.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:813dfbb17d65328bf86e5f0905dd277ba2265d3ca20556e86c0c7035b7182e5a", size = 548868, upload-time = "2026-05-19T10:08:48.026Z" }, + { url = "https://files.pythonhosted.org/packages/0c/72/7de501cf38dcacaf35098796f3a50e0f2e338baba18a58946c618544b809/jiter-0.15.0-cp314-cp314-win32.whl", hash = "sha256:50e51156192722a9c58db112837d3f8ef96fb3c5ecc14e95f409134b08b158ec", size = 206380, upload-time = "2026-05-19T10:08:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/1e/a9/e19addf4b0c1bdce52c6da12351e6bc42c340c45e7c09e2158e46d293ccc/jiter-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:30ce1a5d16b5641dc935d50ef775af6a0871e3d14ab05d6fc54dff371b78e558", size = 197687, upload-time = "2026-05-19T10:08:51.088Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c9/776b1db01db25fc6c1d58d1979a37b0a9fe787e5f5b1d062d2eaacb77923/jiter-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:510c8b3c17a0ed9ac69850c0438dada3c9b82d9c4d589fcb62002a5a9cf3a866", size = 192571, upload-time = "2026-05-19T10:08:52.451Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f6/45bb4670bacf300fd2c7abadbfb3af376e5f1b6ae75fd9bc069891d15870/jiter-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7553333dd0930c104a5a0db8df72bf7219fe663d731383b576bb6ed6351c984d", size = 317151, upload-time = "2026-05-19T10:08:53.867Z" }, + { url = "https://files.pythonhosted.org/packages/d7/68/ed635ad5acd7b73e454283083bbb7c8205ad10e88b0d9d7d793b09fe8226/jiter-0.15.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2143ab06181d2b029eedcb6af3cebe95f11bbac62441781860f98ee9330a6a6", size = 341243, upload-time = "2026-05-19T10:08:55.383Z" }, + { url = "https://files.pythonhosted.org/packages/5d/db/3ff4176b817b8ea33879e71e13d8bc2b0d481a7ed3fe9e080f333d415c16/jiter-0.15.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eac374c5c975709b69c10f09afd199df74150172156ad10c8d4fd785b7da995", size = 363629, upload-time = "2026-05-19T10:08:56.928Z" }, + { url = "https://files.pythonhosted.org/packages/ab/24/5f8270e0ba9c883582f96f722f8a0b58015c7ce1f8c6d4571cf394e99b6b/jiter-0.15.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3b3b775e33d3bfaec9899edc526ae97b0da0bf9d071a46124ba419149a414f8", size = 456198, upload-time = "2026-05-19T10:08:58.618Z" }, + { url = "https://files.pythonhosted.org/packages/45/5b/76fc02b0b5c54c3d18c60653156e2f76fde1816f9b4722db68d6ee2c897e/jiter-0.15.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3071db3346334beae1360b46da4606da57bf3528c167b3c38533afaf9f2c5", size = 373710, upload-time = "2026-05-19T10:09:00.151Z" }, + { url = "https://files.pythonhosted.org/packages/c4/52/4310821b0ea9277994d3e1f49fc6a4b34e4800caebacb2c0af81da59a454/jiter-0.15.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6694a173ecabc12eb60efbc0b474464ead1951ff65cd8b1e72100715c64512b", size = 349901, upload-time = "2026-05-19T10:09:01.621Z" }, + { url = "https://files.pythonhosted.org/packages/93/fe/67648c35b3594fba8854ac64cc8a826d8bcd18324bbdb53d77697c60b6ef/jiter-0.15.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a254e10b593624d230c365b6d616b22ca0ad65e63a16e6631c2b3466022e6ba8", size = 352438, upload-time = "2026-05-19T10:09:03.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/28/0a1879d07ad6b3e025a2750027363452ced93c2d16d1c9d4b153ffd51c91/jiter-0.15.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8d2955167274e15d79a7a020afdd9b39c990eb80b2d89fca695d92dcfdd38ec", size = 388152, upload-time = "2026-05-19T10:09:04.741Z" }, + { url = "https://files.pythonhosted.org/packages/c1/78/46c6f6b56ba85c90021f4afd72ed42f691f8f84daacb5fe27277070e3858/jiter-0.15.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:acf4ee4d1fc55917239fe72972fb292dd773055d05eb040d36f4326e02cc2c0e", size = 517707, upload-time = "2026-05-19T10:09:06.231Z" }, + { url = "https://files.pythonhosted.org/packages/ca/cb/720662d4c88fcad606e826fef5424365527ba43ce4868a479aed8f8c507e/jiter-0.15.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:e7196e56f1cd69af1dbb07dff02dcfb260a50b45a82d409d92a06fedb32473b5", size = 548241, upload-time = "2026-05-19T10:09:08.093Z" }, + { url = "https://files.pythonhosted.org/packages/60/e3/935b8034fd143f21125c87d51404a9e0e1449186a494405721ff5d1d695e/jiter-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:7f6163c0f10b055245f814dcc59f4818da60dfe72f3e72ab89fc24b6bd5e9c52", size = 207950, upload-time = "2026-05-19T10:09:09.616Z" }, + { url = "https://files.pythonhosted.org/packages/93/59/984fd9ece895953dad3e0880a650e766f5a2da2c5514f0eafdaaabbeb5f9/jiter-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:980c256edb05b78a111b99c4de3b1d32e31634b867fd1fc2cf726e7b7bba9854", size = 200055, upload-time = "2026-05-19T10:09:11.367Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a4/cf8d779feb133a27a2e3bc833bccb9e13aa332cdf820497ebf72c10ce8c3/jiter-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:66b1880df2d01e206e8339769d1c7c1753bcb653efd6289e203f6f24ebada0c0", size = 191244, upload-time = "2026-05-19T10:09:12.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/43/1fc62172aa98b50a7de9a25554060db510f85c89cfbed0dfe13e1907a139/jiter-0.15.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:411fa4dfa5a7ae3d11491027ffb9beadec3996010a986862db70d91abba1c750", size = 305585, upload-time = "2026-05-19T10:09:35.995Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c4/dd58fcd9e2df83666e5c1c1347bef58ce919cd8efc3ffa38aeea62ce493b/jiter-0.15.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:2b0074e2f56eb2dacca1689760fd2852a068f85a0547a157b82cb4cafeb6768b", size = 306936, upload-time = "2026-05-19T10:09:37.435Z" }, + { url = "https://files.pythonhosted.org/packages/39/86/b695e16f1180c07f43ea98e73ecd21cf63fa2e1b0c1103739013784d11ae/jiter-0.15.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:913d02d29c9606643418d9ccfc3b72492ab25a6bf7889934e09a3490f8d3438b", size = 342453, upload-time = "2026-05-19T10:09:39.294Z" }, + { url = "https://files.pythonhosted.org/packages/34/56/55d76614af37fe3f22a3347d1e410d2a15da581997cb2da499a625000bb5/jiter-0.15.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b15d3ec9b0449c40e85319bdb4caa8b77ab526e74f5532ed94bec15e2f66822c", size = 345606, upload-time = "2026-05-19T10:09:40.727Z" }, + { url = "https://files.pythonhosted.org/packages/73/38/505941b2b092fd5bbbd60a52a880db1173f1690ae6751bed3af1c9ddcb4e/jiter-0.15.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:631f13a3d04e97d4e083993b10f4b99530e3a10d953e2eb5e196b7dc7f812ce0", size = 303769, upload-time = "2026-05-19T10:09:42.203Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/a06692b29e77473f286e1ec1f426d3ca44d7b5843be8ad21d7a5f3fcdcc0/jiter-0.15.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:b6c0ffae686c39bf3737be60793783267628783ea42545632c10b291105aee45", size = 305128, upload-time = "2026-05-19T10:09:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/7270d7ad41d6061a25b950c6bf91d638bd9aacb113200a8c8d57a055fd67/jiter-0.15.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d54fb5b31dea401a41af3f8a7d2512e9b6a6a005491e6166c7e4ffab9639a9c", size = 340459, upload-time = "2026-05-19T10:09:45.452Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8d/302cb2057b7513327b4d575cff6b1d066ee6431a5357fc3f8867cd684406/jiter-0.15.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d5d6090cdc1b7c9e780dfb04949a990adb1e301a2fc0bbcee7de4638d33f9a", size = 344469, upload-time = "2026-05-19T10:09:46.864Z" }, ] [[package]] @@ -2899,140 +2894,140 @@ wheels = [ [[package]] name = "xxhash" -version = "3.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c8/d4/640915f28a551050e299a2ba6194875de7bfe7e0ecd1be79eb429fcb8a74/xxhash-3.7.1.tar.gz", hash = "sha256:9de50caa75baeca63bcb3b0eb753508a5cddc7757682444d650684bc4ebe1095", size = 82993, upload-time = "2026-06-24T08:21:24.065Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/ae/2d1a44b26968a8de810ad60d1e75ceea0c360b59af9040adbd07da6dbdee/xxhash-3.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a548acf955337291a8bfcf8a648adb4a339e58a20289c27d0ed74aecf71a8ed6", size = 33718, upload-time = "2026-06-24T08:17:05.515Z" }, - { url = "https://files.pythonhosted.org/packages/2f/62/65bb4c406c42ba49a345a70270dc8cdf9671d275c78060a3daa16635158a/xxhash-3.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc808dfa211c9abc8cb0d30308ab3fdbf88a11e9fa526fe67b59fcd144c3369e", size = 31082, upload-time = "2026-06-24T08:17:06.609Z" }, - { url = "https://files.pythonhosted.org/packages/5a/bd/a798f0c43c76c2adef6d8f19f4e98981f4c4024807da26b79d022afe269c/xxhash-3.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0ffe91c645a84154a98d2700475d87977b143a6790f7fb64556e29cb076bec32", size = 195104, upload-time = "2026-06-24T08:17:07.89Z" }, - { url = "https://files.pythonhosted.org/packages/7c/86/30914509b4943da5a7be8c11e9cb8b145abcb492d3b4de57a00de33cc368/xxhash-3.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:43d7a5abf13c22377ce4ebc995ec7452c1a69e1903114438775afbeb00bb4e48", size = 213994, upload-time = "2026-06-24T08:17:09.246Z" }, - { url = "https://files.pythonhosted.org/packages/73/b7/186ce246a374e10aa616a32e661db73b03d41a4d9eedcc6f9cdccf1f8836/xxhash-3.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cd5216faf84dcc46b0110e47bf78ea80fc1ad113862454a67ea46b62286126a9", size = 236947, upload-time = "2026-06-24T08:17:10.67Z" }, - { url = "https://files.pythonhosted.org/packages/eb/d7/ced1473c28c07dade11944af6cb81d924750a41d667bfcfd5d22b3d8e555/xxhash-3.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cf78e23d37f5a546961a2d7a90708c48f331ca9e64149324dba5168f46ecae4b", size = 212700, upload-time = "2026-06-24T08:17:12.043Z" }, - { url = "https://files.pythonhosted.org/packages/b4/ba/23edcea3e24a4ca5ec024154073be7b6b5f1592546ab0784e87aa58c8b6a/xxhash-3.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8160907bb7847e126803d74ea3b7c90aeb275416b2fe2563ac5c6be8733113d0", size = 446211, upload-time = "2026-06-24T08:17:13.556Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ff/6bcb62ba7a12f5db21bec1d963001949d926963fbe1e0b1fc057c6cedc8d/xxhash-3.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:078b5e52184c5ac91e63c42bce205f3c84b8231f11a40ebdee3cb8a4913a7644", size = 194259, upload-time = "2026-06-24T08:17:15.213Z" }, - { url = "https://files.pythonhosted.org/packages/d4/3b/24fffe8b61a38e49a4994f7fa8543a6ea54f01845e222da8a35719995024/xxhash-3.7.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1fb8f77d5ae1db88a7f28441b44d01a3dba6e4850e772c41b158c635d4650d9f", size = 285536, upload-time = "2026-06-24T08:17:16.597Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b0/432b7080f395c3c89538d523cf324e83e252ae37a3eceb4d844c1d7e5c48/xxhash-3.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ed8cc9f7b72bc14591fd639e0ae98bc22f4b217fcd9ab96bf706386c7605964b", size = 211235, upload-time = "2026-06-24T08:17:17.941Z" }, - { url = "https://files.pythonhosted.org/packages/bb/38/94549be8aba0f23b04fd7aa971659d5dcecdde585b66d18c3134349077fe/xxhash-3.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0f359bfb4ecd07be3f51a50e4b9feb4f37722f5c0ce690a4e194db4a333b5033", size = 242216, upload-time = "2026-06-24T08:17:19.401Z" }, - { url = "https://files.pythonhosted.org/packages/c7/77/7b86daf734105faeffbf4454ddca83572c8fedd9e790953685b1d3db646c/xxhash-3.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6b62fe766d1a573bbd1a6d8d3958c01f69a7465bd686452487fa9fda4d3d48b5", size = 199096, upload-time = "2026-06-24T08:17:21.086Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e5/4f4351beb10d3d9262b0998df89b7e8e83e8567305ee5672cc040b640200/xxhash-3.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5dccf79f20eb4416cbd8194c86487bc8f2bc8ed6910299e54379b359a1173c8b", size = 211169, upload-time = "2026-06-24T08:17:22.512Z" }, - { url = "https://files.pythonhosted.org/packages/08/3a/72c7bd3656b517f810db2b251758f75648630447887a1626d807638813b6/xxhash-3.7.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f1423ed4e840f31a52bddd6b9f65f16920f69387b1241c1fb902e10b4d3dfe76", size = 276206, upload-time = "2026-06-24T08:17:23.9Z" }, - { url = "https://files.pythonhosted.org/packages/90/fc/4e9cc8fb3ef6f2aca7c9a9a2294b89733209884ac6326a2d88459fe2d1f8/xxhash-3.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:be9b4a7e27a507535eea4c85adf3c71befdcfad81030a221062edb7f4b989bd0", size = 414422, upload-time = "2026-06-24T08:17:26.127Z" }, - { url = "https://files.pythonhosted.org/packages/32/57/3c39655245cbdcb070444e569ea8d78c4946ba2baf9e9a6842fb43a24284/xxhash-3.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:265495f9de9c448658a0382dfa3bd2d777416096de9e4734797034150e7f347c", size = 191783, upload-time = "2026-06-24T08:17:27.751Z" }, - { url = "https://files.pythonhosted.org/packages/38/16/553f41229cf074c2ae2e66b84d9de35a39d75d83fa06abbdafbcd9fa5f35/xxhash-3.7.1-cp311-cp311-win32.whl", hash = "sha256:c4f1b1de45362c0a2e0a196498d97d95d6f04a50e7435a047cf2924f53248183", size = 31001, upload-time = "2026-06-24T08:17:29.252Z" }, - { url = "https://files.pythonhosted.org/packages/7d/96/1ec33621acf3aec83d252697ad9968c753d986a98c89d37d28169b01956e/xxhash-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:e5f40e8bb83f58d05997f5c7c11651dc369be09b881eaca4c98b2a70c60e9804", size = 31831, upload-time = "2026-06-24T08:17:30.743Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f4/559ce98d3386d267169e9c0a13dad51a03d9140768fd00186ca75921fd0f/xxhash-3.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:60055a8fc14a4c0688eaac8c77dc2b1cc9ab5c4c80ce4398a1d9c350b7ffe05e", size = 28119, upload-time = "2026-06-24T08:17:31.96Z" }, - { url = "https://files.pythonhosted.org/packages/0f/0b/2fae11b5a51a1e713be151f709cca63bcfc3039804a972e242e48ebfebdf/xxhash-3.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a1adf2649445fba8ae80ebe6777b4a92ea5c6c866ea37fa426e22474eb9d5bf2", size = 33651, upload-time = "2026-06-24T08:17:33.054Z" }, - { url = "https://files.pythonhosted.org/packages/c7/cf/ff8cce70213e64618875ed1ccacba32ec3484f95c7949bf69bd534e0eadc/xxhash-3.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40241adc0e7f48990ccb1604e8a61c8fe2fd29d6840fb3c24f29a02b58e293e3", size = 31108, upload-time = "2026-06-24T08:17:34.423Z" }, - { url = "https://files.pythonhosted.org/packages/cf/47/45b9437d3759b3db58d7a57a60590d82c7299f92c9ed4b968e9f207ef354/xxhash-3.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8ba8526a6eee3e36eab5f084e509bb672ba10f127e92b732b21643993672be93", size = 194488, upload-time = "2026-06-24T08:17:35.949Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e7/8a56baba72e426643fc07ab0408ecb25f6f338e228c99b309853e12212b2/xxhash-3.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef90eeda11c3c72ee10749550b44016a4ef31812d747f05a63d347af31198202", size = 213345, upload-time = "2026-06-24T08:17:37.724Z" }, - { url = "https://files.pythonhosted.org/packages/19/2c/51e5ad9923d6f153a907ee038870e4e32c4cf2abc7f7fc46fa97683d5a8e/xxhash-3.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:04f5789e424d896599f5d2b8c815ef8677606471db61781f69b1c6984cb2d301", size = 236681, upload-time = "2026-06-24T08:17:39.201Z" }, - { url = "https://files.pythonhosted.org/packages/c3/36/c2a9d450ffc82fef5f4398b569b6929eab39015aa20811f2133cce2763e6/xxhash-3.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:93feec0ab868dbace1bc0359718b6212818103d6d92e18cc283032ebc05d766d", size = 212456, upload-time = "2026-06-24T08:17:41.129Z" }, - { url = "https://files.pythonhosted.org/packages/18/2f/773cc4821661db54212bee593d5dc0ce829d56a3b1f7a299503da706c702/xxhash-3.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6169425427cdc735ade703873c433bc8ced7f7b8acbc149fbfc5232ae8afd66c", size = 445829, upload-time = "2026-06-24T08:17:42.805Z" }, - { url = "https://files.pythonhosted.org/packages/2f/84/afca49532c9f40b35bdfac76c15edc038e8526e239e6712aa033229601d3/xxhash-3.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54d1b92b2d1b462704191692f99f820e2f74434bc29e13f2eb845516d4345008", size = 194273, upload-time = "2026-06-24T08:17:44.26Z" }, - { url = "https://files.pythonhosted.org/packages/d4/9a/02b14faa4ab626c64e4c5555e8e35ae7979586f4d1f5fe4cc29ba18e51ce/xxhash-3.7.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f3e5eea0c6a387edd80fc4c08d88d3f8c73d33dd614bf9e8d0ac3fd1eed6c4fe", size = 285168, upload-time = "2026-06-24T08:17:45.707Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2f/3ec42637069ad05938c2b667bbcd26443492d30c70622c45e1c093c792e3/xxhash-3.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4b98be721a75741f570c10117190e6ba353981d9ec339ca33ac891e9481622f", size = 210781, upload-time = "2026-06-24T08:17:47.411Z" }, - { url = "https://files.pythonhosted.org/packages/ea/7a/911b2c6ea96af6fd01022c3b5feb7146acd68d228d1a23a2d1f5aa77b9e1/xxhash-3.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75c6ba10f94209ee2087ea13344a4816fe85d349d8ef4c6e55e0fb7b2193f2df", size = 241502, upload-time = "2026-06-24T08:17:48.919Z" }, - { url = "https://files.pythonhosted.org/packages/75/f0/6f819204a51f6fad03c7d71f1fab5f7721018c2df7b0bd88abc93585f04d/xxhash-3.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3519176963dd11825e92f283b31dac566bf3cbb6b59e2372037170f9a7611643", size = 198415, upload-time = "2026-06-24T08:17:50.682Z" }, - { url = "https://files.pythonhosted.org/packages/be/55/45c547b6c4eb25bdbd07ef33a9bdbe0a2d1ea50210e471cbbc99b7f763e6/xxhash-3.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:797e4a780f6eae0abf99e1519b42ea5cd93d031004c80fb49385d4a210b47f68", size = 210976, upload-time = "2026-06-24T08:17:52.26Z" }, - { url = "https://files.pythonhosted.org/packages/dd/95/f04b07674041734aef5d177e9960354723a9f6b1cc409f749af716df8b7e/xxhash-3.7.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b13072d4b2f8cbe208908c38ad454e16a0a2323bae7b9698c89c33bdb93da673", size = 275718, upload-time = "2026-06-24T08:17:54.131Z" }, - { url = "https://files.pythonhosted.org/packages/08/57/373fa329b9b1119efc2b42b85ed4b2c1fc7bd0882154f52b55d8c1c61028/xxhash-3.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:48e1af1959ec0e5bcc65eff8e66e47e27d2d65d69a0b2d1e0b7023c70cb52578", size = 414477, upload-time = "2026-06-24T08:17:56.088Z" }, - { url = "https://files.pythonhosted.org/packages/4f/62/0dfb6a2a5ed7b12827135cb50f029041748a3b930c243068ae9a93880928/xxhash-3.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d51b0b339d8ec1615e797d16771fbec541fd23cc4ee4af110af55d8116255e40", size = 191815, upload-time = "2026-06-24T08:17:57.718Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4a/7bc1d0464e04442eae256f33ee87656ae92a0554f91611e456393277d8cc/xxhash-3.7.1-cp312-cp312-win32.whl", hash = "sha256:3c59a4e0dea8ff1ae7fc1169504a82d61334c82896e95be8fafaa865e78b3a06", size = 31054, upload-time = "2026-06-24T08:17:59.263Z" }, - { url = "https://files.pythonhosted.org/packages/7a/99/77004b3e68e4eeb64d3f13c93b33b3fecdb5f8bf94b9140d95c0b24ee93a/xxhash-3.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:751ad2244e4b0dcc719143a9b52efd6d4ab3a6e0531de9aa422a542ef078e38e", size = 31876, upload-time = "2026-06-24T08:18:00.477Z" }, - { url = "https://files.pythonhosted.org/packages/70/fa/af63f28ba11b6d1c6e7646449e7eae6a7bd5c9f958a6d4f82b582fa1b03f/xxhash-3.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:72ef63bdd9806277218471b145298f93f942b8710cad8644d039bb8637317412", size = 28129, upload-time = "2026-06-24T08:18:01.602Z" }, - { url = "https://files.pythonhosted.org/packages/4c/1b/c439778b529a437d9a0cfebe12b8bf4145a32a282b4bf18dee920b9accc2/xxhash-3.7.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:4db20366b104ef337f56820abed761712ba6c5260db31d6422b5aa3cbefa50e3", size = 37089, upload-time = "2026-06-24T08:18:02.915Z" }, - { url = "https://files.pythonhosted.org/packages/58/41/a02809318b408c9fccf0c2d683a748b5150b057955f18d64d6777d5f09de/xxhash-3.7.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:0383bc26f83de80fdd7a9d675ff2daf08b4d7716c520268098bfbeb6dfeede14", size = 35331, upload-time = "2026-06-24T08:18:04.216Z" }, - { url = "https://files.pythonhosted.org/packages/2e/c5/a52eb0b76b31e7132f18d7ef04edaf4ab533d35bd879f2f9123b92fab969/xxhash-3.7.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:af94492aa899eb87e6437c3618be68b8df1d995d22a2712f6c44f4d2397444ad", size = 29867, upload-time = "2026-06-24T08:18:05.346Z" }, - { url = "https://files.pythonhosted.org/packages/4c/73/aacb1f5168081e6ee80165f8a313e0b62fa92c9be9459b137a80775f57dc/xxhash-3.7.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:cd5a296f72edf5aabe58085fa86a693b7b31f06bd1515a44b310b884e61a617f", size = 30881, upload-time = "2026-06-24T08:18:06.521Z" }, - { url = "https://files.pythonhosted.org/packages/17/9f/250e1754686b6bfa700b8eb27af4f1608c0936577f9e400d2a3621a04170/xxhash-3.7.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a77e6224808ce9e834e89d1d0e75e7b959fde7fff7f34c53cac4ba88094c6cc8", size = 33622, upload-time = "2026-06-24T08:18:07.734Z" }, - { url = "https://files.pythonhosted.org/packages/59/61/924a174bcca4cf83e5045ddb5ed7d12980f31a37bffa4660e8dcd71359ba/xxhash-3.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c81c290353c80431b27b0d756db527e882ad086e6c96606ffd57003510a2abbc", size = 33654, upload-time = "2026-06-24T08:18:08.87Z" }, - { url = "https://files.pythonhosted.org/packages/91/a2/23d37b03fa3ca2610067a4fa5c7dd8b464e1c24f4674a2471e313b0a5bb9/xxhash-3.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8ec6c8c01edfc0dde6dcae655951cac7f328920e6d8516e085d335093792937", size = 31109, upload-time = "2026-06-24T08:18:10.381Z" }, - { url = "https://files.pythonhosted.org/packages/b9/aa/cb383b010f7eda6c7456c54f403940b16cd02fe7e23cb2575e58d89a85b3/xxhash-3.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52843733c427ebdd6da8397f69478d013fca0ce4ae990fc5b823f8bc17416c50", size = 194556, upload-time = "2026-06-24T08:18:11.712Z" }, - { url = "https://files.pythonhosted.org/packages/35/7a/117292f36f5b12191e95551e74a33f02719393428eabd1480f0eb382162f/xxhash-3.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:862ba3f1dc1fd5fc6d0952277f4e9953c31abb6f703bf80775095b0dcd4e64e8", size = 213395, upload-time = "2026-06-24T08:18:13.534Z" }, - { url = "https://files.pythonhosted.org/packages/3a/28/9e14ccef87d5ad3b5b39e72183eb4dff129a0a07ed5177f1b79a80ccf76f/xxhash-3.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:33446d6f3cab84c184bffa7575f16a3377ba4249c003b2ab3a667a986f76e314", size = 236454, upload-time = "2026-06-24T08:18:15.615Z" }, - { url = "https://files.pythonhosted.org/packages/18/10/f121f3a40428f5fb2b2c85d4005fd1d82387589d44e7466b0c6538cf3a7f/xxhash-3.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00b51129f16517d1cb16b19a3171905f047464e8a3ce0d978ef38cdb7cbd742e", size = 212512, upload-time = "2026-06-24T08:18:17.23Z" }, - { url = "https://files.pythonhosted.org/packages/ed/3c/26aa2e7cf89c2ceebb5344bfb0df52bb8498657aa22e6d527cc03d22b049/xxhash-3.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb222c844962d50b5b3985e3e4d67d63de96bf40a03f603300580da09c8a826c", size = 445900, upload-time = "2026-06-24T08:18:18.97Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e6/ab9152424b6e1bbaf2155377c42018fbe6806d8971773e2be05a67fa3139/xxhash-3.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b827658b9a7f43158d9950f736e57ae1c18280294098264bdec82248d79cb29b", size = 194302, upload-time = "2026-06-24T08:18:20.522Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6a/2462e9a7cf46ceb2f12a6def03ff759627c286e99e37efafd21f48ba7049/xxhash-3.7.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aab1f506d3d2b29ba62eac451033e3f8330f4e54e74e7cf300f63152878ae880", size = 285212, upload-time = "2026-06-24T08:18:22.22Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f8/173838bccab8900a680aa61911faa34d2a52e5de52a38ff853781c4e98d9/xxhash-3.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:364bfdb6a2a1d477ee1b06ac12c656037a666b869b15cbde1eada97e9562e612", size = 210815, upload-time = "2026-06-24T08:18:23.783Z" }, - { url = "https://files.pythonhosted.org/packages/d0/11/f38c82fd64c813028c3ae2aca93c62466ef645995a9d42d9d10b38f2725d/xxhash-3.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9f21df182b39a02b727e579eaea524331b48f3f2701fb8df69bc94b7f166c878", size = 241563, upload-time = "2026-06-24T08:18:25.687Z" }, - { url = "https://files.pythonhosted.org/packages/0c/26/60a33f86917ba48584978d0709777c2fdd6fb541b5783c05f8e9949889fd/xxhash-3.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bdf977b8dfa2586f42d6711d15a6d38c7adbd57ba4861151807bccbef67ca911", size = 198483, upload-time = "2026-06-24T08:18:27.352Z" }, - { url = "https://files.pythonhosted.org/packages/e1/03/5a4fabef8b4b3d85fcfefd808f833f56fd541ff9c48df4839136066a6612/xxhash-3.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdb7b3182b3812f992ff83f51bd424eb933dbc1a8a46f1d03176fcba3ae045f2", size = 211005, upload-time = "2026-06-24T08:18:29.262Z" }, - { url = "https://files.pythonhosted.org/packages/df/9d/565af3aff6fd409f33bfa0e747a10a1dac1babb98ede8d2a5a00ebc5a7a9/xxhash-3.7.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:7648d4ce61e6cbcd340cd5d69a5f56f95b0d0e93a09dcc822d97092bef7b9d3c", size = 275768, upload-time = "2026-06-24T08:18:30.672Z" }, - { url = "https://files.pythonhosted.org/packages/5b/2e/474816c84712920ad5c6a592f4837d78250bfa1762c6d5e9bb9ad00252ff/xxhash-3.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a88d6f65bbb5429db37181bab8268003512655f534bb01420a8b3295f8923d55", size = 414540, upload-time = "2026-06-24T08:18:32.628Z" }, - { url = "https://files.pythonhosted.org/packages/22/cb/c6e86f20c68538c960736fa26fc4c01f45619232229d06781d17d19fa55a/xxhash-3.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2e0c4481e42921250eb6b709b12975f2f57bce5af359d8f603860faec67307e5", size = 191854, upload-time = "2026-06-24T08:18:34.412Z" }, - { url = "https://files.pythonhosted.org/packages/a7/1d/3a1b9dc8ceb6ef5b083c3cf4bc6db6297795e001531cde901d706c711e0f/xxhash-3.7.1-cp313-cp313-win32.whl", hash = "sha256:66225f0dcd672b5d547e00bc57a16debde48fded3cff45d7102c9fc3d809b086", size = 31057, upload-time = "2026-06-24T08:18:36.865Z" }, - { url = "https://files.pythonhosted.org/packages/6e/67/6f05319a2d7d3587b8250d8dccbac11ef5b565931a6aaae5e83a764ceaa3/xxhash-3.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:f96b3bba2c255072e2c5e8cf054c164e3b6b9263dccd5cd1291e5d595ae6cfa5", size = 31882, upload-time = "2026-06-24T08:18:38.233Z" }, - { url = "https://files.pythonhosted.org/packages/92/72/b80d1f22a0e3d0db0b79fb7923cd237f39bfdbced9e1a513e1271dff48ba/xxhash-3.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:9cc83af5306edd49095d037be0770ca5274ff92d56e5f7cbf7c59e65911d68f5", size = 28133, upload-time = "2026-06-24T08:18:39.627Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ad/60fa52b034447e698881374c828ea9a4ea18a4b8b7b36f9eb6ce8fd49fdd/xxhash-3.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cf37e9ffcdc29b07677e917c3674da8db59f45a06d11121a94961e0b69fe2d1e", size = 33848, upload-time = "2026-06-24T08:18:40.796Z" }, - { url = "https://files.pythonhosted.org/packages/41/f8/24ea82f0f0b2c3a9d40340dba69b144f2827da231f88f3485f33effbddfe/xxhash-3.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdfd9d39b6ff62adaad64a72a54123aff09b8806146db16848c4e6561b9de48a", size = 31346, upload-time = "2026-06-24T08:18:42.218Z" }, - { url = "https://files.pythonhosted.org/packages/5f/84/b6fbca0911375db1f1d835385fd3a31d26751e7aa2b219187fb84af44fed/xxhash-3.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8f23cb81bfd88f3fe7d933376bfb45f4fc0c595021a54628860af805046e0eb6", size = 196894, upload-time = "2026-06-24T08:18:43.514Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ab/5acab22b45492bb316e9b5016d222f0a65083fa4e0f1b876871bde58431d/xxhash-3.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a86bf6437021cfb110e613910d4cb7ed62f44d2d8ae04d91b14600099d7e3ebb", size = 216106, upload-time = "2026-06-24T08:18:44.876Z" }, - { url = "https://files.pythonhosted.org/packages/5e/56/9b9f225f4d0f850870e5520c45f61627d4889c7db5280a489086c0f8a3cf/xxhash-3.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d2bcaac2714a1e90f615cffd1638b7012bf3de3a9c9629f2fb66e025ea7fd00b", size = 238307, upload-time = "2026-06-24T08:18:46.616Z" }, - { url = "https://files.pythonhosted.org/packages/f5/0e/18e011e692677753bb7b2e2ea0b17bf26f0c4efab593147727e53fe11f97/xxhash-3.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8abf100453e8fdaef32a24eba235300ed2a1ca0f41f505459f824a0e934e120", size = 215103, upload-time = "2026-06-24T08:18:48.296Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ec/6d1d0f97a9f41839bae275ad38a144f27e8dd9f507f38aef9fcc93b734a7/xxhash-3.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05b16b82533fbd45376e01a506bbeb1121375448c8b941ae6dc5a879b1c9fc6a", size = 448775, upload-time = "2026-06-24T08:18:50.12Z" }, - { url = "https://files.pythonhosted.org/packages/03/5c/51fbb3071c0e9e8c6835dc35c7f6f0a2e6dbd63703b39760e0c53ac8a111/xxhash-3.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4335a49585aebd2a6755e5f2b5049878095356477e2de7e68fe5b1bedccbedf", size = 196412, upload-time = "2026-06-24T08:18:52.067Z" }, - { url = "https://files.pythonhosted.org/packages/30/4e/e3c6fe30de88c848b302bdf2ac254de24a2df2161d7167fe67db3561d70f/xxhash-3.7.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d33231b08db1da77e2199e56e6cf11949a45884999de7b954d80229724aa0f92", size = 287213, upload-time = "2026-06-24T08:18:53.543Z" }, - { url = "https://files.pythonhosted.org/packages/8c/aa/cfde2f35de71992e4fd03b0356f476353a04a6e2b99a5795a26f43d22063/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c3fde1048f949493b2081c62a6516f33da018984a63165c90146ab102f93b900", size = 213346, upload-time = "2026-06-24T08:18:54.992Z" }, - { url = "https://files.pythonhosted.org/packages/f8/5f/cbbefa93e55532fafb12b43d649ebdfbe5567ac5978099db86e1882a4649/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:1bfce305b29b36604a1b0ce7796872cffe5365f41500f01cddf945ce50a2038c", size = 243921, upload-time = "2026-06-24T08:18:56.669Z" }, - { url = "https://files.pythonhosted.org/packages/7e/98/33a7d7f613d4611386e7be8772d67a2d5cbecf863a1b2d9cc6009e26b8f3/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0a0ea48d1e9aa724bb21d9ed5b083420eb8edbe0c8a0d02f7a29bb70bdddba57", size = 201285, upload-time = "2026-06-24T08:18:58.414Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e3/fc0f914932871822d860aaceb5e1bd66bd3795b69ae2c212c0368b862379/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b09310fe99ac2070b65b0eff03d610a67f89793fedcd7c5b48688c34430254cc", size = 213584, upload-time = "2026-06-24T08:18:59.9Z" }, - { url = "https://files.pythonhosted.org/packages/f4/7d/952fe6f6644e0938c3d18a29bc1560b5a3eaaf5f69c0340df1f028eeff99/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1c32cc0566e852be920ef226a925da2ccccc826c5b1b1bc707373dfd76a459a2", size = 278078, upload-time = "2026-06-24T08:19:01.83Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b5/bbb1d2d46f514ad87d05a4df71ff9f718277d47e9896d48883195ba5b080/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4601e38c2750a744b985dc8245b00eba29ac4619c4c3b29818abfca4be73c1ff", size = 417279, upload-time = "2026-06-24T08:19:03.757Z" }, - { url = "https://files.pythonhosted.org/packages/b6/7f/0949a73192ab6a62a1067498a89c4d70786f8eda75c2ff67559d4cfd4717/xxhash-3.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a89cfe5077008f3713c1e2bbf508b3adfc638d4ae7209220e021192daa7e31c2", size = 194176, upload-time = "2026-06-24T08:19:05.298Z" }, - { url = "https://files.pythonhosted.org/packages/43/bc/8c62c8291068e58455ce462035f36f007676819e675d818b8d5cc230b44e/xxhash-3.7.1-cp313-cp313t-win32.whl", hash = "sha256:199710e41e0921e4e696635b1b29deca9739ef5961190e793691d3a8b8d1716e", size = 31341, upload-time = "2026-06-24T08:19:06.727Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/c37bed241ee9a96333102f2f439fdd8aa37750a9622518fe4881014c073f/xxhash-3.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:500c5d9b69d43c0acf71d29ce82a0ca9048aea64d2fe6e38af7b53caec7ecc72", size = 32144, upload-time = "2026-06-24T08:19:07.952Z" }, - { url = "https://files.pythonhosted.org/packages/c1/19/97ac96e8238cabade19580caac10ca6cd8ad21111459f4fee2791d63675c/xxhash-3.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:b5af5023fa6a6cfc9fd0f36813cec1e859ba021157e6ebacd8b8900011fd939c", size = 28293, upload-time = "2026-06-24T08:19:09.289Z" }, - { url = "https://files.pythonhosted.org/packages/f0/68/1010395c711cdf676d1eea08909c57e519b41c549b70511e8192161db0f7/xxhash-3.7.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:f56347991b9b40402ac50acb1bef31c31c4ae0b33a5588f9e093b30ccf99d2a2", size = 36891, upload-time = "2026-06-24T08:19:10.465Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d0/500655d067d53b156b02e15faaf3389536e2b65d15162c86e0c06733369b/xxhash-3.7.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:09ed80c2f2f7d23b28729cc58ca8bf0565e00f59125c1648e7e5c80d88e47a92", size = 35220, upload-time = "2026-06-24T08:19:11.752Z" }, - { url = "https://files.pythonhosted.org/packages/ef/2d/dd95e9b492084fc590284be4e8271324172e2b64124d7831d5c32925d6d0/xxhash-3.7.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9eee9540b8c360bfe40cc7809b93187877ecfc054d2ef8d213dd5547dab8dd65", size = 29873, upload-time = "2026-06-24T08:19:13.281Z" }, - { url = "https://files.pythonhosted.org/packages/26/60/4b16f199f262c0463db075320ac71a57a5b367823c5b49b9ec381a5e89b4/xxhash-3.7.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9aa27f83a2b585a2b1daaccc40c51036213ebcc1b10a32d164620ebb802fb20a", size = 30893, upload-time = "2026-06-24T08:19:14.555Z" }, - { url = "https://files.pythonhosted.org/packages/88/d9/538be2550d2af6c43ef3768a7310b99a4c2cb63164017f430b0b9ee4b6c6/xxhash-3.7.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:b831aec61952cec525f12c09b260389e39d811a36b923f734c6e50ffde77bb20", size = 33621, upload-time = "2026-06-24T08:19:15.732Z" }, - { url = "https://files.pythonhosted.org/packages/c6/74/e22ea27aa664f256df508cbd445371ba51b90120e00f35c16d22d35585d8/xxhash-3.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:dea90966f1aee53743b049f9cfd5ff1993e5ef89e56fc8f6bd00e96436b2c6ec", size = 33758, upload-time = "2026-06-24T08:19:16.893Z" }, - { url = "https://files.pythonhosted.org/packages/a9/39/1b85d17393d6c592fb80487562e2f248603c447da3627507ffb52ad5748b/xxhash-3.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05d36de6fce47315dd97e572ab3e9a28b0c511ff5b7208b7d5317587a0a336a7", size = 31120, upload-time = "2026-06-24T08:19:18.109Z" }, - { url = "https://files.pythonhosted.org/packages/c2/0f/236c32562c62c7e5d0b21684cf8d1c0ed55122dbce10ad299727c4c9f21c/xxhash-3.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0e1ebafe872c8d787d1b392effccb87ca0fb812829037da94bc48807dbf79e1", size = 194750, upload-time = "2026-06-24T08:19:19.39Z" }, - { url = "https://files.pythonhosted.org/packages/eb/64/811bbf8763edee40b83959ff04112d5f131bb49911073bf39427196f06b9/xxhash-3.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d5ed80d41b056c9d0d2be2a0cd8d0d22462be6f2143bb59068426da0b509613", size = 213507, upload-time = "2026-06-24T08:19:20.857Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a7/8a0d21204e116bf3d494266cb822ad94a99552479b0060b5745453fa0b28/xxhash-3.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:842950f1860bc55a735011e48765567912502cbbb04033ff0efc06ec5842dd16", size = 236592, upload-time = "2026-06-24T08:19:22.29Z" }, - { url = "https://files.pythonhosted.org/packages/f1/6f/ad5fadb89766120a1393a434d770e14f94dc7ea146e49188910faad83ecf/xxhash-3.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bc500efde92a813436b7fdbbffd51b7320f71aff1cb09d44ac76d31a28ddc5d6", size = 212676, upload-time = "2026-06-24T08:19:23.652Z" }, - { url = "https://files.pythonhosted.org/packages/53/15/99ca84d43b265d48fe5ce5beff02cd9fed5470d8f1b74b3cbf0e43f1e176/xxhash-3.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9b6a61304b66cfb8667f824a31fe26999c936aa50897034fa9b65adcef088c5f", size = 446020, upload-time = "2026-06-24T08:19:25.129Z" }, - { url = "https://files.pythonhosted.org/packages/1e/b6/95e2756867949798386ae5205560495c0243a78cb2a750ffaf069c6a6c34/xxhash-3.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f9a6863d0472c0298e87d6da7ab8c9c49e7072bd7a9303ae04291a9600b6ac2", size = 194387, upload-time = "2026-06-24T08:19:26.746Z" }, - { url = "https://files.pythonhosted.org/packages/54/4a/0c8e3fb9b054a97f37097e163c448266f2f7eaf8ff9f566cd722704c2357/xxhash-3.7.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c5581325e442394a97d996fae3456e0ab9ffc57c1d78d16f7e499858e1de636", size = 285299, upload-time = "2026-06-24T08:19:28.443Z" }, - { url = "https://files.pythonhosted.org/packages/0b/8e/f55f741dadc9b6c6cc8569242683813ffa9c54ef39dd0e769cf937c05423/xxhash-3.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34731e2d8c607bd1d9697b174f9a08f9ea655274fec51e2d66f16b3d40b8837f", size = 210925, upload-time = "2026-06-24T08:19:30.067Z" }, - { url = "https://files.pythonhosted.org/packages/be/e9/d4aaf1c213336fadd92c816ed1ca1d86bdd9d45e802f637f5e38ea771a99/xxhash-3.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1f05c06e71eab1809f017990ae2bdfc63ec961f2a6a2df6d68e5dcff60c0f39e", size = 241668, upload-time = "2026-06-24T08:19:31.544Z" }, - { url = "https://files.pythonhosted.org/packages/88/88/1c674952eb4265521deb5c6cda47952040e74a0813a7933b3a1e3eae2e7b/xxhash-3.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ee9962b423b06824f08ee4849cf8560b7d630ea67c0b1bc21ded2275f899c04", size = 198768, upload-time = "2026-06-24T08:19:33.065Z" }, - { url = "https://files.pythonhosted.org/packages/a7/de/dd6c1b20f6fd02983b22e0907d31b387cf59b6f0679f3bcc160490990178/xxhash-3.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0cb7322f8635cb69ac771e1bf529e6cbea458d1df84e653b0ba49d045aaff8a5", size = 211108, upload-time = "2026-06-24T08:19:34.526Z" }, - { url = "https://files.pythonhosted.org/packages/9a/6c/06f928a7dff78c83bea8ed154ed4ec91be49a8ed4862cc14011d5f6d0ed4/xxhash-3.7.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:156136c6bf615f74e73ba4ddd4b355e1469408745410ef95c3d33b99c0bc1821", size = 275937, upload-time = "2026-06-24T08:19:36.222Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c8/80a5e87f369ede8c82dc3d45fb64a8bedfd5288f63148bbf9759fa30c7f3/xxhash-3.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:81cccac441335635af32b69ddb1d28400094aee8ccd4c80bacf5da61747f600f", size = 414554, upload-time = "2026-06-24T08:19:37.781Z" }, - { url = "https://files.pythonhosted.org/packages/d5/78/cfa9350dd577ae0d27aac2aa6294ee038fa4ad9f4ef7ff4869516916e583/xxhash-3.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f5007843d543fe17889c0df0b02f82df81e28a21a8a498609ccad2b14d0f74a4", size = 191939, upload-time = "2026-06-24T08:19:39.474Z" }, - { url = "https://files.pythonhosted.org/packages/82/cb/d8439f4e662f1393019871fbd2f1145d66a55717b6cb6f32bb873132ffd3/xxhash-3.7.1-cp314-cp314-win32.whl", hash = "sha256:62be5960d81bc06416871aeab8b26a21ebab4650fa3e51f900aa7b59cc5ab618", size = 31656, upload-time = "2026-06-24T08:19:40.906Z" }, - { url = "https://files.pythonhosted.org/packages/56/b8/948c4367d1de11e29d94981e4b79a18fc805e763c9009d0b36cca43a2154/xxhash-3.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:6cb3008e57b1a837c841d344d802abb133ae80296f39a519031bdb4b7a8b547a", size = 32639, upload-time = "2026-06-24T08:19:42.191Z" }, - { url = "https://files.pythonhosted.org/packages/10/4e/18635110d4c28d4badff26e86860cedaeaf83efe90aeef694eb2792b47a7/xxhash-3.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:e1e84128aca5db8aa402d63385ff878a69409f5097dda22209aab182f94d8823", size = 29049, upload-time = "2026-06-24T08:19:43.715Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d3/eea194b5ecdbc4ddc19d60d0b88eee0e11dbae846b6e4763916ccb8d8b19/xxhash-3.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:86fc508b4109eb496139055573233807bbbc7def84d4585ce8fbad005ed716a9", size = 33920, upload-time = "2026-06-24T08:19:45.383Z" }, - { url = "https://files.pythonhosted.org/packages/68/4d/f33bfbc55eb20c4d0789468135fd1dd62ea0e620dca6f259f2d919f2cc14/xxhash-3.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:673d58460a3596f6a72cc7f1efc387cfcade965e144011af2ce76e03823ab1e7", size = 31347, upload-time = "2026-06-24T08:19:46.59Z" }, - { url = "https://files.pythonhosted.org/packages/66/04/743a84186307f61b21ebcc5256f22cf5c0c7b8f552ceefc53dc866166883/xxhash-3.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c671fd48a49c6e4ac0d5ac785f2f7fb2c458dc8dbb52d1eb933dd66271560e47", size = 197035, upload-time = "2026-06-24T08:19:47.936Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c8/1d1fa91a193248ae7fde17a898aa28cbffd3a62716acc760b3faf1b8816c/xxhash-3.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d9d72bf4811ad101560a5dc2ed30257584123ca33f8ab56461c0dc7b6e23b9a", size = 216203, upload-time = "2026-06-24T08:19:49.413Z" }, - { url = "https://files.pythonhosted.org/packages/f4/cc/684b585c2fe6177a38f7d9d31ea3e58f1d6a5c8ff569490c67bc158c26c4/xxhash-3.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:05f6d7f9d6a4fc1f135a2c6a2c42757ad1846ddd5d8790dd1e8559679879536c", size = 238379, upload-time = "2026-06-24T08:19:51.136Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c9/645da0d06e404bc02373f5ac01f8a0b805cc95d482a8c742acd177a33f90/xxhash-3.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f7b2d9dc97ca0ee8c39dc3d1ce643aa4b584ec5999a8255ee7035c02efb69a5", size = 215225, upload-time = "2026-06-24T08:19:53.227Z" }, - { url = "https://files.pythonhosted.org/packages/e3/23/10b269565f7774defabc55e2f4a8611415d89f4f0d8f8346aa3f9bd15f01/xxhash-3.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:47113e60f4bab943e40ca984aa2ee40bb2bade72e079a02ce56e497f215a5a05", size = 448904, upload-time = "2026-06-24T08:19:55.024Z" }, - { url = "https://files.pythonhosted.org/packages/2f/45/fda5848405462389ef3728450782e8454a6cf1646354bbe35789cf9a3ce7/xxhash-3.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd9ddce013e2ebd36e13fe52ead4f76be3cf39a3764299bd9962420fa9c6a458", size = 196504, upload-time = "2026-06-24T08:19:56.582Z" }, - { url = "https://files.pythonhosted.org/packages/04/ed/6df8ae0f16fdc70716ff3689005d141503584e946c5c94d52804a4595e77/xxhash-3.7.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fdcaf46c36ffa40120c63c816f5de2d75c10876af894eaadfd51abebaa5888a7", size = 287360, upload-time = "2026-06-24T08:19:58.411Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d0/2a1dfb2b4c15656ca8913118538c40a7481c689a7118cb6b1351811e2591/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f712ee8ae977fe6e941a6c7d29d2415c2b635b3c0c56f0e0fe745036f5176bc5", size = 213431, upload-time = "2026-06-24T08:20:00.13Z" }, - { url = "https://files.pythonhosted.org/packages/c1/c3/21e5ed79eec067fec447d1b36ea405fc87052c49ad05a3e6429d43ba6df9/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:43c35d20015ed2108a0e2a02a941694bb9fde154bb6c1112f8b39d35961ad09e", size = 244089, upload-time = "2026-06-24T08:20:01.747Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0a/91c7c16ab909e68698b6ec9022db6dfcc6814bc6494cb01056a31dfea79a/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:7b67a394f06d499b8525ad98b84f77c3b226cbfba22c35dfcc7425fc7b1f473a", size = 201401, upload-time = "2026-06-24T08:20:03.357Z" }, - { url = "https://files.pythonhosted.org/packages/54/48/e1880f94057f9c3c6998a68eef849154781bb8b527a1320c75ed7050b32d/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:39ab6df3c9d76f477c7b174fc77b6e7fdfeeead6899699539bfb3850494c3d22", size = 213677, upload-time = "2026-06-24T08:20:04.915Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c8/baeeb1d4b846fca2e313ee8d65c426dd4560548c5e1354b80666c12abcd4/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7db59b3293c8cd6c2ca9854f1b246502f8dd0b72caa4dc755b85c10594fd427d", size = 278210, upload-time = "2026-06-24T08:20:06.498Z" }, - { url = "https://files.pythonhosted.org/packages/19/b0/d01ab517da006259dc00fc89b32f39acd42492756364417e957371c5bd09/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fd73cebdefbdc6aaccc910041ad192426279ff87a856274d1d6e202ba2546b96", size = 417417, upload-time = "2026-06-24T08:20:08.099Z" }, - { url = "https://files.pythonhosted.org/packages/01/10/582cf987173c4b7e272396ede2694005fc204612acaa5d13e74761f017e1/xxhash-3.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7a285830529716693c72a463a4313b4862482730b308afbb1c91a208eab7330d", size = 194291, upload-time = "2026-06-24T08:20:09.805Z" }, - { url = "https://files.pythonhosted.org/packages/3f/df/61beb9e061ddaa349f1a6e75ef14f174af1439de0308d63f2561039eb522/xxhash-3.7.1-cp314-cp314t-win32.whl", hash = "sha256:02ad31001ca4f8241b5edc38f009a189d075f270b023e1a1ba01b1aadf7240a8", size = 32026, upload-time = "2026-06-24T08:20:11.383Z" }, - { url = "https://files.pythonhosted.org/packages/78/b1/c6a5ba0ecba582d9302daf67fdb13a5081b122ea5ce18aa6df3e59001cbe/xxhash-3.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:68ee1b9903252e80437559d32ae6d0a752e6a26fa6416925c7e6282500948838", size = 32887, upload-time = "2026-06-24T08:20:13.004Z" }, - { url = "https://files.pythonhosted.org/packages/c7/88/70b85fdae219f77d8e70035cef3d6527e4d000aa7038ff046279d47aa39a/xxhash-3.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5795037fb23aa33bdb1d6daa6a68b001c4462de447c1a31917ae1aebca691286", size = 29160, upload-time = "2026-06-24T08:20:14.297Z" }, - { url = "https://files.pythonhosted.org/packages/b9/6b/5e244d6550e17c0beb3decdc30a7d87867c4f00df0c00a5c9115d20acd56/xxhash-3.7.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:70debeb180feba224a0ba2bc532e643ea7e94d21147ecb1770124cafcf258aa1", size = 31386, upload-time = "2026-06-24T08:21:16.236Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a6/924b9b6b05a9e345f19cc5890b84bc4843495aa88867e131d9fde42bc454/xxhash-3.7.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76abdff52f009610e80969870c5397e74d5f8d8c511ebb418ad166e788f0614a", size = 28804, upload-time = "2026-06-24T08:21:17.488Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7f/79e7fcab1057a20f2359deef24a5a63ad0395a6dabf3cdc577ab5a310a87/xxhash-3.7.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0666d8933fbf52141864357c0e4c48fdaec6138f4459a2fc0c9fc5b581902b22", size = 41385, upload-time = "2026-06-24T08:21:18.827Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a8/40aa7fee7a0f9a92ec708f5a437a7e4100cb365d60661bcd8bfdc5af60bf/xxhash-3.7.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25451c853781de56b41a6b45160b6d62ab9c313abf97501539a3984599427b40", size = 36582, upload-time = "2026-06-24T08:21:20.117Z" }, - { url = "https://files.pythonhosted.org/packages/64/15/b86254d961ef925bee70aeda0cfe769cc35a81f546bfed47823b5db6df07/xxhash-3.7.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81c5bc7de50c23002577f44bf7719497e54c41ded0da8acf223f1b3823c541b8", size = 32808, upload-time = "2026-06-24T08:21:21.416Z" }, - { url = "https://files.pythonhosted.org/packages/75/3f/9036bc57048a72707bf97bc7632335ad32f6047301035de46d7cabd14e5c/xxhash-3.7.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:da6f19cb2a75fe8417ca03b40c7efdcfeaa0d24b9cae782199498b2cb0a29911", size = 31935, upload-time = "2026-06-24T08:21:22.657Z" }, +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/ed/07e560876a4458987511461187b285071f53cde49dd5b25cd8c51091522b/xxhash-3.8.0.tar.gz", hash = "sha256:d72b2204f37840b0f16f34192c09b994b97bd25823d723d47a1eddfacf06eb43", size = 86107, upload-time = "2026-06-27T08:17:28.798Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/1b/73aaae7755372ff0cd5788c9955abb64b34d519dd84f2f4f081e2082119b/xxhash-3.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08c34553cd7ceb3bfcfca344dc70305a45430429b5d58a67750f2a58364f638f", size = 34641, upload-time = "2026-06-27T08:12:47.579Z" }, + { url = "https://files.pythonhosted.org/packages/53/08/fdb1cb1001ed15b1f74a8eb70457dbdcd6df8375e27e3fe0d0225dbab170/xxhash-3.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:842d147983110e5a4f533f98f4f5bc851a08c7ca00aaa30649e8d5f9a6d4e47a", size = 32316, upload-time = "2026-06-27T08:12:48.695Z" }, + { url = "https://files.pythonhosted.org/packages/d7/05/c004e99c4292a9dde76c9157e8e51c73c6db2dd7e4a876712e6a6113e3b0/xxhash-3.8.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:37c9943e18f569f76a8b7d5d01bfe0716f7762c396096ceb42a47eb3d5ecf641", size = 220196, upload-time = "2026-06-27T08:12:49.964Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b2/8696a2008d59c3dc9346b26f7d64f5ec342cacc4051664e3b0201354fe58/xxhash-3.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21f6797afdc7abb0ffae059a0d1619c84a5368115bc0abd48f9803ab56a5d35e", size = 240908, upload-time = "2026-06-27T08:12:51.544Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/2415c55a17f525bcfa38b5b51d69381d6485b1c320eff373b263403b5e6b/xxhash-3.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5875d99d3540367d43779551dd22c813420b84a103e418d791095b9808fdca57", size = 264445, upload-time = "2026-06-27T08:12:53.08Z" }, + { url = "https://files.pythonhosted.org/packages/23/25/056d30ed2e500d0a993e4589da8cdbe50cbf4809c1b1ac84f6f9559d99ba/xxhash-3.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1a54ad5a2a96cdf1ee7a935d38bc63daa6095530095a916f644f1ab76604ced5", size = 241295, upload-time = "2026-06-27T08:12:54.703Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/5d8c9b65ae05725c2ea8f331705e1382fc4817911eb159450aecb2905c6b/xxhash-3.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b32e50dd85f0b67b2b95eb59cd3242052f6b27b70e9e73b27629686c592e3ea3", size = 473113, upload-time = "2026-06-27T08:12:56.159Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d4/734dd8e6eaa03b0c4e3044127755221ebf153260a3c5de0382430486fcaf/xxhash-3.8.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4208fb85c950ddf7118b040bca15179c3bf9b7eb8bebe5e6ef067fc8af16a7", size = 220001, upload-time = "2026-06-27T08:12:57.869Z" }, + { url = "https://files.pythonhosted.org/packages/5b/cc/a0d92359d499db55f83fe6de13188125515319b968bd627b591a0984c454/xxhash-3.8.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9f17e09b035f2a0139536da53deb392b62ee259dc2a2189be12b06a7dd50489b", size = 309757, upload-time = "2026-06-27T08:12:59.438Z" }, + { url = "https://files.pythonhosted.org/packages/bc/dd/a20949401cfb9c940ef858d93b41ded90382ff4be0f7e8a5249edd95ff18/xxhash-3.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7d6dbb976d6e3b3be51bad16b13de7f4980e6aebd0aa51c5a14dfcc0fedd495e", size = 237596, upload-time = "2026-06-27T08:13:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/6963ee0c245a69d9c4a2583da603915f9288f1df23700a0ec705239ef014/xxhash-3.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:281897e5c516769694c999f5c50fd1e9acb27acbff187282a8ac77c38b6a9be5", size = 268683, upload-time = "2026-06-27T08:13:02.577Z" }, + { url = "https://files.pythonhosted.org/packages/db/ea/3489cde91ccd91230efbb2351a6d9358e8a63a9954cb8f071fa9c32a2558/xxhash-3.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8fba3d08c246201a1a0a6cece53a0b3b0890fc16adbe1edb245fcfcbf4eb0ce2", size = 224882, upload-time = "2026-06-27T08:13:04.21Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f6/179847064c92a07bba7381e9cd7132c380a17aad31e176a2d6f6e73eed48/xxhash-3.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:14ebc1559e8a9a481d0d5506b87678942fcdfa794d4aa55cdd2a0fb175d4245a", size = 239563, upload-time = "2026-06-27T08:13:05.96Z" }, + { url = "https://files.pythonhosted.org/packages/2d/83/dd599670efd161d31fba4149e20694f140ae5707068d38ac480dac1c8cd5/xxhash-3.8.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5e7a3e3bbe3a56bff70acc9b72576670e793b0184de3d1b9cda2bf697d17f630", size = 300148, upload-time = "2026-06-27T08:13:07.495Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a8/a474f136610594b464ad813f6badf00b931211a69fc86542c21daf5d2a4d/xxhash-3.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c71e3755a8320d29c351126d550930349be22b44bac1a559caf12ab78b53e9f", size = 442448, upload-time = "2026-06-27T08:13:09.467Z" }, + { url = "https://files.pythonhosted.org/packages/75/86/054032919fc73b72917054cf731be76be3a984e8f53b1d0ba6f22fb9cffc/xxhash-3.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:715c611582004e75010517b919776c5dbc00aae03054dc9fd72484a23fd1862f", size = 216755, upload-time = "2026-06-27T08:13:10.902Z" }, + { url = "https://files.pythonhosted.org/packages/3c/16/2eb382a78f12e3fde1c735b57607498c0efe897e8859484d69d9446bba55/xxhash-3.8.0-cp311-cp311-win32.whl", hash = "sha256:41a30a1d0ba978238742a374875c15979e0faed0a65294f3ff4d9410057ee8b6", size = 31851, upload-time = "2026-06-27T08:13:12.281Z" }, + { url = "https://files.pythonhosted.org/packages/dd/53/a07ad4dbdc32118b3bd190f5d54ee2ed28c1a0a994b52ae493435cfb4de7/xxhash-3.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:43705f917b8b817d6994851bf3725b98b4c95e64186404d9a6dbc1acf12fd140", size = 32655, upload-time = "2026-06-27T08:13:13.394Z" }, + { url = "https://files.pythonhosted.org/packages/e1/87/d76bef62a288a1f2441404b33cb757047cf555cd5956b36ed718a38b81e9/xxhash-3.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:35c5d843bb7ac1dfdb125ef4181fe4c2e01c2275856e6b699de89e9eb5c69c8d", size = 29128, upload-time = "2026-06-27T08:13:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/17/2e/4b7c3ab28b7a54ac17eae7e02471c49609d6fc5900856a455feeb847a2a3/xxhash-3.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fc4bd14f873cd0b420f6f1ff5b5cd0dbfeb05b044a11bb9345bcbbf9749636e3", size = 34623, upload-time = "2026-06-27T08:13:16.696Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/09eea3e1bba6a59d64599cb8fba39f2a0872d06e85420eae989a4da61a9d/xxhash-3.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31904979198e913239cb61b49f5b849696aeb3b03340da815d1491ec74dcc602", size = 32318, upload-time = "2026-06-27T08:13:18.036Z" }, + { url = "https://files.pythonhosted.org/packages/01/59/688bbae31e4e2d6d6eb92acbd3837c0e44ff8c7d435e6da922844ff6efda/xxhash-3.8.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7338ad13f2b273a1ef0ea97b2db0a059fdb3a1a29298bfa145937c0e4152d341", size = 220461, upload-time = "2026-06-27T08:13:19.311Z" }, + { url = "https://files.pythonhosted.org/packages/2d/de/71484ce0dab2fa4a475705d1ebc37a17ff02d40e5df6767b3255cc53120e/xxhash-3.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54e80e803cb34c8a1d278b491e543af40a588d288589c3e6becc991d5328b46b", size = 241110, upload-time = "2026-06-27T08:13:20.844Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f9/1ac88f02e7df7898541490260b21f2b7f7bd2b233038a0cbd3a3b1bffdc2/xxhash-3.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:353953ea18f5c3fbdd13936fb536aacfb47d5bc06eef0919b1a355df61f7cc31", size = 264779, upload-time = "2026-06-27T08:13:22.485Z" }, + { url = "https://files.pythonhosted.org/packages/25/49/7ea1f128d2fe948ed679020f97a0896cdc6c975da5cc69b53a4a9c4a5def/xxhash-3.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d761f983a315630eff18c2fec7360c6b6946f82748026e779336eb8141ef3eba", size = 242609, upload-time = "2026-06-27T08:13:24.277Z" }, + { url = "https://files.pythonhosted.org/packages/a0/da/7d237278dfa1c48722c31010c84a328a317b8885429c8cb6ae4a8fa3e3db/xxhash-3.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f3786a9beb9a3b76241cb7db5f5388b460682c12204236389e3221963fc626a6", size = 473472, upload-time = "2026-06-27T08:13:25.877Z" }, + { url = "https://files.pythonhosted.org/packages/9b/5f/980fda82620a07d80026b4df371cbca12fca0fd94d7087c4ec5d898da76f/xxhash-3.8.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c94f5a9a775f36cc522fa2a7e8e2cec512e252d2ac056759f753dc68a79ffc", size = 220374, upload-time = "2026-06-27T08:13:27.366Z" }, + { url = "https://files.pythonhosted.org/packages/14/71/efa37bc3e91e1c801972bcef99eab877fcbd17ec10aca16c550ee2951107/xxhash-3.8.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:55ce59f9af37ac861947b43ea3ce7b294b5de77a1234b558d0f07ffad0197624", size = 310220, upload-time = "2026-06-27T08:13:28.804Z" }, + { url = "https://files.pythonhosted.org/packages/9d/48/19e40320044dc7051e8446505f18557d5661853b87a8770ad399325bb3c8/xxhash-3.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3afa1422a32c7c8e79ad5121dc21eaa5cee9e9e67bffca3f15d15d220d371908", size = 238100, upload-time = "2026-06-27T08:13:30.378Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0d/588499f4d7cd064864ada7adfb9e8785f88a988f1332ed4c1be73d249c15/xxhash-3.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:551fda694938be910529452a89175137c58b4739e41fadff3c047e24b1d74a3b", size = 268937, upload-time = "2026-06-27T08:13:31.867Z" }, + { url = "https://files.pythonhosted.org/packages/54/18/fb2ad593572a33d1b6864b33047b8ca7269273a3c56107b5fd33e0b9c8fb/xxhash-3.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512eb937c9457e6057e230e005c4709dd2ab63a5989f854d69f31db905750a62", size = 224910, upload-time = "2026-06-27T08:13:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/63/9e/b880f9ed61b73492e24bb962d76aeb63f18ccb895f0edfb52e20d45ed6f2/xxhash-3.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4931ea93840f750a908efebaf23c71004feacc1a4649ef601b96d400a505c9a9", size = 240742, upload-time = "2026-06-27T08:13:35.237Z" }, + { url = "https://files.pythonhosted.org/packages/3f/89/fc682f93e54e486fc338b26a7d6d0d5cb0ab366269273c2608ac62b51afb/xxhash-3.8.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2fd4b60e8d9fc3923f39079f185b3425e6d76636fcb66d82a33dd7eba7c30f2f", size = 300527, upload-time = "2026-06-27T08:13:36.997Z" }, + { url = "https://files.pythonhosted.org/packages/80/71/a4b4122afb2d17ad69e0922cfeddb5ad5c25b02f37eed3dd3819d42e5f55/xxhash-3.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1da00075f1605794298878cb587f7533329693e2a0c45bbd25d6353644add675", size = 443195, upload-time = "2026-06-27T08:13:38.719Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e5/ed3930f5dc90f4b1bab5ac3be099e8b2e81c1262d85e4adb5f2758e30d23/xxhash-3.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba73801c87d44fa37b2a5feab3004f0a654506027bf032ceb154d94bb74ea772", size = 217252, upload-time = "2026-06-27T08:13:41.179Z" }, + { url = "https://files.pythonhosted.org/packages/44/ae/128ea5794387ca54bb4084566db20dbdfc9c21cb17b67d3fcb403927b5ba/xxhash-3.8.0-cp312-cp312-win32.whl", hash = "sha256:0b0836dee6022e22ba516ebfa8f76c6e4bda08d6c166c553e40867bac89e4a54", size = 31890, upload-time = "2026-06-27T08:13:42.568Z" }, + { url = "https://files.pythonhosted.org/packages/4f/04/a6c182dc566c88e8d1a497d22cc4ffdcfcc0a9fa80325efa6cd4b9002c54/xxhash-3.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3bc2a09b98b8f85c75208cd2b2d2aecf40c77ecb2d72f6bf9757db51a98d3499", size = 32677, upload-time = "2026-06-27T08:13:43.705Z" }, + { url = "https://files.pythonhosted.org/packages/93/b5/aeda4e79f962c8d58ec60cb20a5abfe91c9f7d62e626f69f6659bc0bd0c4/xxhash-3.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:208e6a8b93426896d803224e9fabe26f8b9c651e8381a80b1fa31812faa091e3", size = 29155, upload-time = "2026-06-27T08:13:44.903Z" }, + { url = "https://files.pythonhosted.org/packages/ec/1f/96f43c5c7c7c4d44721f8d2e5d74698c667a30283c4b10a7e50a56804ee3/xxhash-3.8.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:36434c1d1b0a4729df1fa26ab11bffed1ba52666c0beb605c98a995b470cd143", size = 38508, upload-time = "2026-06-27T08:13:46.152Z" }, + { url = "https://files.pythonhosted.org/packages/1c/d9/7d5d6af4876c6481f2e0acb2dda64dd5209574bf7ba1ad4f6af7a1f8d473/xxhash-3.8.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:a5e6497cefcb2d67f1745c66df9718a99112583af6cc2b70da0312a2eb939f1e", size = 36542, upload-time = "2026-06-27T08:13:47.497Z" }, + { url = "https://files.pythonhosted.org/packages/32/ff/66fed439d78c5a09a1491a85af29bf8923b516530116731a9ac6b14dee2b/xxhash-3.8.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5b00b82f1be708da9404fefd658cf5cf3be5ee3be2aae4bfe3b874255badd342", size = 31102, upload-time = "2026-06-27T08:13:48.721Z" }, + { url = "https://files.pythonhosted.org/packages/56/b8/9fae0399281095f8aca1f32b21947b3c3c75ad6021b255c5c6e4b11d3866/xxhash-3.8.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:38b0cb0ab7f283413b7cace2bf710d7cf8f702ea82cbc683908691d52028a89b", size = 32096, upload-time = "2026-06-27T08:13:50.138Z" }, + { url = "https://files.pythonhosted.org/packages/61/a4/e53d162c74a8a2950dc063969914387b0680da4c7c20ad17744ec03a3b0a/xxhash-3.8.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:084312171a9798dea85e924b2674f5e1a44933050a1ea1cb1c6b1364e004c66c", size = 34585, upload-time = "2026-06-27T08:13:51.572Z" }, + { url = "https://files.pythonhosted.org/packages/69/f5/e12397e3f2c4917b6572e103a3277cd27cc56330e304bba61d195d7e5224/xxhash-3.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6a1a9e845bd3bbc57d9356819e0d198fe23282e0576b398a6282a0f8fdc75aef", size = 34622, upload-time = "2026-06-27T08:13:52.818Z" }, + { url = "https://files.pythonhosted.org/packages/70/80/c053dc51af5c942229689a0e9cb66fdc999bbd840f645e761f5ab73cbb17/xxhash-3.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ffbde09743ebaf8957b8426948fbe85eab5e5de0d29eec407fcff5a2812a3cc", size = 32320, upload-time = "2026-06-27T08:13:54.04Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a3/294171b67dfe770e1293edcf2a3f7e41302cdb8aefb258585312191b3ffe/xxhash-3.8.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a6dee3952c2b6e82e7f1dbc5dbc6167f9c84126851def7926e32827c2816169c", size = 220532, upload-time = "2026-06-27T08:13:55.448Z" }, + { url = "https://files.pythonhosted.org/packages/80/c3/d141bfdeca785c8c680abf867d4b52a5e64a55d90df242c3141a3e58c4b2/xxhash-3.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf8ff8e12416c9fa05b43c7509b9332d6ffc4090413c4e7a1dee8599763b6d59", size = 241215, upload-time = "2026-06-27T08:13:57.047Z" }, + { url = "https://files.pythonhosted.org/packages/09/5a/aeaf35143a6f3d44db73298e861405bdd9c9dacaedfc369cb43d9fd65282/xxhash-3.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cebbb322df4d97d8ef2704f49ed2f6f21f6702fafa0dc0c2a6ae70e904205689", size = 264615, upload-time = "2026-06-27T08:13:58.912Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3e/f8ca782bb34f99693faab70a7989bcc84f62ffe93c9a4cca464a33507a4b/xxhash-3.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9a8d08707b4100ebce598fc59fadf04b42d79b855818d6994f8f0fffd1df8edb", size = 242682, upload-time = "2026-06-27T08:14:00.483Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/ddbee4ff1542c2e88e72269a5a6bd18c3f26a80c2514e0918f5d1f3e9ec5/xxhash-3.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cf5427602dda15d8ce3c6d870d29bf07d43975f59c9d6d3f7f6f93a901b28b12", size = 473551, upload-time = "2026-06-27T08:14:02.17Z" }, + { url = "https://files.pythonhosted.org/packages/25/f5/a680d48dddab37ab2fd9189ca03f775e29e3627122e30790816d7eb365af/xxhash-3.8.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97d7bd715ea5050b6c9638b52c62adf3055b648ef6eee6892a4cd9697b530191", size = 220485, upload-time = "2026-06-27T08:14:03.765Z" }, + { url = "https://files.pythonhosted.org/packages/22/b1/7ac129b74981c07f1ff9c649f204465e86f83f9f29b2ebdc70d91514c365/xxhash-3.8.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cd25bbbab37d898f6e5a90905ce6ae2c1f8bd6668c07cef406fb3e8c8c570dd", size = 310307, upload-time = "2026-06-27T08:14:05.366Z" }, + { url = "https://files.pythonhosted.org/packages/67/e6/43e673411249dd63f6cd974523a1b32fad75cf5453e363bc8f44af215fb9/xxhash-3.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3e30e5c057f483c3c53a11b53eba091a737cb19dfead36c8b23bf5beb4a169cd", size = 238164, upload-time = "2026-06-27T08:14:07.149Z" }, + { url = "https://files.pythonhosted.org/packages/e5/95/87f8baf41f63130f3637104b7a610f82b20106332fc6e289c8dbf7955d0e/xxhash-3.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:07dd44d992ebd456752bc25b1c42cd172d94bd8cb24049300449ad0716081c3a", size = 269062, upload-time = "2026-06-27T08:14:08.834Z" }, + { url = "https://files.pythonhosted.org/packages/38/c9/3369b497cd1f926b930c52fd2400606f177790d887b49f9e86bddcc24562/xxhash-3.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3118600a3102d4707dc1c485dbc3acbbbf37819069ad3e7854e77b923745d76b", size = 225007, upload-time = "2026-06-27T08:14:10.689Z" }, + { url = "https://files.pythonhosted.org/packages/34/c8/03dceb86a8128858ac105bd6e282d62b3db6fd421a79bd8a9f6b8cdc47a7/xxhash-3.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ed37b0c95d8fb3fbaad5e13cc0a9727eb8739d1d54b2adef28108c250cada3a", size = 240815, upload-time = "2026-06-27T08:14:12.195Z" }, + { url = "https://files.pythonhosted.org/packages/47/a5/ebd43eeb1af1dd8f0201943688b20958e99d3f6eb36481fb8c37b55ef139/xxhash-3.8.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bb043da412e478e7b1db3407051124b85b133803794d3809ad6d92870b304fc7", size = 300632, upload-time = "2026-06-27T08:14:13.916Z" }, + { url = "https://files.pythonhosted.org/packages/df/24/c873e41a3c00dacc385c8ff08c007723f6a528922c1cea7fd9684e86dae7/xxhash-3.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:196fc132683d9311a0bdce8388ee52bfa07fdc1987cc428a27956e47ccd7b50d", size = 443293, upload-time = "2026-06-27T08:14:15.446Z" }, + { url = "https://files.pythonhosted.org/packages/4f/1b/c671272fe28f70574e3c574d58465f26460154bcc68876121872afa1c14d/xxhash-3.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfb5411af3b77c75e99db100aa15c5ba623c85d72c565e4d7a0ed1a986ff766e", size = 217327, upload-time = "2026-06-27T08:14:17.28Z" }, + { url = "https://files.pythonhosted.org/packages/57/43/b45a52f795812cb769b6ac159e69b605d18b1c067749e63dcac159e90064/xxhash-3.8.0-cp313-cp313-win32.whl", hash = "sha256:6d1d6179e26830c6690fac63f76d372f69714b977e12ca9c42188a60f51c59f5", size = 31898, upload-time = "2026-06-27T08:14:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/a1/42/2bd70e4eec25dc5990652979d708d4d7c999793d7d5af5d0e48ab4374dc1/xxhash-3.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7c92427a56a12f4d5c7bb26dbb9e9a4658c313ecb6c2f1dca349902e3822df07", size = 32680, upload-time = "2026-06-27T08:14:20.277Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c8/2fe61edb6144183cf094035a8c5354c65a073127acf6379655ed1e705b70/xxhash-3.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:9fc8453642c1c6d38b4fbac8901c2452ce1fa88b27f003bfee6703cbfae9bd63", size = 29157, upload-time = "2026-06-27T08:14:21.674Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b8/81d17a993b9a4750ba426ce966421681bb4b8e82a460cd346756491b8cc2/xxhash-3.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:efcacb644a915f010dc477447b045e5dcde1afaa40d16b2f0f8e7cd99c9e1635", size = 34897, upload-time = "2026-06-27T08:14:23.044Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3b/f5a368e3273440b3ea58fbd3f0b08c19f552b25ca59f43f5732ca96d2126/xxhash-3.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d1e0dbc510cff94c5efbcc2b82c28b41519fad09b5b1f9f3d99c63e3940e49a0", size = 32630, upload-time = "2026-06-27T08:14:24.603Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ab/f424359c91c55f564fbbe4e454a126eb522471109f67376f20ad19c5e663/xxhash-3.8.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ff19d016a41c90d1f519005887191896b6da1274e1d5d48b347e17eb798ffc5a", size = 225874, upload-time = "2026-06-27T08:14:25.992Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c2/434579ef9235123b6c9bfa89c5614e0001e988613b91557b24aa326d9faa/xxhash-3.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aafc3eab99c50508852e34307e9565933bf128cad084cac7d2471b7ab1743de0", size = 249705, upload-time = "2026-06-27T08:14:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6c/3c0c917331ca3c71f826cedce2127f230624e2b49b992472dd5e9e72101c/xxhash-3.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5e521368ed79ae6c4d31e1e417726643c49d7d6e286f4fdabf9a8330ed8a8ff7", size = 274716, upload-time = "2026-06-27T08:14:29.495Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f3/a8bb98d3307c67e88be9642dff52854c3de3f488f95989b60ff69c8dcc42/xxhash-3.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6a0127688d116ec0c225e7e1f744e3f206de2b8822ffeb31a9ab5cc6384f92c5", size = 252019, upload-time = "2026-06-27T08:14:31.247Z" }, + { url = "https://files.pythonhosted.org/packages/f7/73/fab69a2e5b6353dde643209fe9b6adf4fbd64c888e531deffc476bfb2635/xxhash-3.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:22c0b17da2f9fea0f8836538512249871b359141616bad44c58d238b5f011f40", size = 482024, upload-time = "2026-06-27T08:14:32.973Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5b/ba34099b5278097ec9c68c0b740719813553bfd11ca17e7353de6d2a41e3/xxhash-3.8.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d49465646b1a5e3b1729c5f636e05676a2fb52e203e3b22a5411c416c4c5302", size = 226655, upload-time = "2026-06-27T08:14:34.608Z" }, + { url = "https://files.pythonhosted.org/packages/76/0c/90aba4708a37fe752b324a7cbf10058eaa33e892cdd62751ff17a5137b93/xxhash-3.8.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c2853dea1e30ed00ca87dd87d76da5da063d302b823b3fb80ccd18421de0f251", size = 319583, upload-time = "2026-06-27T08:14:36.419Z" }, + { url = "https://files.pythonhosted.org/packages/38/46/42e349e2d3017b2688f4cb301742c37c438e77963e3fef711edce2fc5c65/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:82f0102a2a3760287b7cd7f9e0a30edd4c3b18762ed1a242208d43c8e2bcf30b", size = 246000, upload-time = "2026-06-27T08:14:38.104Z" }, + { url = "https://files.pythonhosted.org/packages/ee/15/741b947ae3c768e82018c46846f8616f6aa9b5042649f318a1a6897defe3/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:b8414a66a7524596d841cad5dc1adab6ce76848db5ab2b83db911fbdab1417af", size = 275455, upload-time = "2026-06-27T08:14:39.841Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b4/a9db84c9458fc8f53eaf0051377d1e9eecd9f330fb1225640027417a309d/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0dbaa73df10414ea1e41b98691a9d8241d4c47ad8d02c726587a3cda05278e53", size = 231209, upload-time = "2026-06-27T08:14:41.543Z" }, + { url = "https://files.pythonhosted.org/packages/20/92/60a868cd34851746d0b0d95dced0f42867c7c00606f6e5dba85b70b232ce/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:43fc9aaba10ab4267c90793601f60d35c3c9caa1544eceb483618a71ad9ce7da", size = 250416, upload-time = "2026-06-27T08:14:43.193Z" }, + { url = "https://files.pythonhosted.org/packages/7a/6a/168ca46a4679c32aae9246caa1fddf35981d6304487e45e992b3d4530324/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ec5eb3d28fbb9802c6d2526f772133a06c91d6f03756fcc67c834b642ffdd51d", size = 309764, upload-time = "2026-06-27T08:14:44.79Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/13646b348c07679c818791ab2d35415db5cb20f3bc77daaa255909a401b4/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:2b77c301b644cd9b4d0749a3291081ec2048a6bef7fe0487c993bbba3efb9ce0", size = 448650, upload-time = "2026-06-27T08:14:46.562Z" }, + { url = "https://files.pythonhosted.org/packages/59/9a/3d244b2acf6bbd86a363817ee09084b4684e8e11840663e19869e9e0d952/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d7ece11a132325353890a144c30119073617a1299c593ca29b96c315b07e1edd", size = 223572, upload-time = "2026-06-27T08:14:48.294Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c7/143410d026a6e0d86dc69037ec2a3b8db810a54e7f443b340ac17612be2e/xxhash-3.8.0-cp313-cp313t-win32.whl", hash = "sha256:b21db84df7b9d54d9e4195a964243c1b32d745c6fbc0cfcfffee1d4bd297196a", size = 32301, upload-time = "2026-06-27T08:14:49.687Z" }, + { url = "https://files.pythonhosted.org/packages/6c/db/2240b0638161637b2f310231748a7a6a06c79fb43a3adb34c96f359762bf/xxhash-3.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0643b7d9f598f6da6f1f6b899f4358250d0fb853242e2d712cbde27bf5a99d29", size = 33221, upload-time = "2026-06-27T08:14:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d8/52038e4fa5baf4f00654a225516168d02908edfec7ca104fbefc58af394f/xxhash-3.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:4bbacf2e938526969f8ab3334d4ac3da14ea059e1dfd1339a92f9091467e750f", size = 29294, upload-time = "2026-06-27T08:14:52.778Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ef/a09907aa28bdcdf6810d5c26656b154c60c0f06bb8db8442a1192d9c227a/xxhash-3.8.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:557e2a7cc0b6a634cf9c8e5c975d96b7da796fdeb1824569d760cf0f25b6f33f", size = 38365, upload-time = "2026-06-27T08:14:54.166Z" }, + { url = "https://files.pythonhosted.org/packages/d2/4d/d991ff77bc489c2231025e64e570502156d573c7bff69c917589cc307089/xxhash-3.8.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:dad744d1613cbfddb844dad93adbffbd51c3e9f53ceea9568f7c3b94bedc19a4", size = 36477, upload-time = "2026-06-27T08:14:55.427Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0e/553eab001f1e274da73da074968cdc8be8cacfb318937ab9871b8e1909cb/xxhash-3.8.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:953f29b22c04b123cf3cd2e08bccde3a73184aeda5a1038e0054cb3355644120", size = 31116, upload-time = "2026-06-27T08:14:56.897Z" }, + { url = "https://files.pythonhosted.org/packages/55/d5/d0f4dbe7b4d9ce0125f16e45ec0be5e04f6a172edb4e2fa551c4f2eb5d7a/xxhash-3.8.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:aa699e0253ceffecf41cae858d0a11f2439d6874a0890b556387bffe11dc1c08", size = 32112, upload-time = "2026-06-27T08:14:58.126Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2f/b332c7bede6a676343f2c9c8dea233c8c82753eaeda6f7a2c321d8c58ca3/xxhash-3.8.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e232c82466babc13e956d53aa84d0149660ed6886bc195248bb4d03bf2eca301", size = 34618, upload-time = "2026-06-27T08:14:59.458Z" }, + { url = "https://files.pythonhosted.org/packages/b3/5b/2bf3c9e61c7cf8f53bce937af45e22b72bb1f224d5afb20352beba0d628d/xxhash-3.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7f75fd1c6a5028f345cd4a8c52f4774d2e5b7809fa58111c60a5502b528914a4", size = 34739, upload-time = "2026-06-27T08:15:00.863Z" }, + { url = "https://files.pythonhosted.org/packages/64/b6/e88521f5736c181b89bfb7ab756f0ca658a8a1ecece7277b75e167717614/xxhash-3.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b49d7e09b211a1ad658dbe2dbf6561eb92f2e6926bd1101e2d023178371f2d6f", size = 32332, upload-time = "2026-06-27T08:15:02.383Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a2/fba440739fa5f86d2c28738c202e88d3dd063290c8bbb20e183c5334456a/xxhash-3.8.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ceb702bc8e56b7f1f1413d42aa294045b9a0e4c9888e07edc5cd153e8c4c948f", size = 220479, upload-time = "2026-06-27T08:15:03.785Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1c/4a1639efec16416695d6c7bc6b224d3f607e0b8cbe2409fa81081a849d1c/xxhash-3.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f3c96e06bdb122e8cc84f5c7088579f3102b828efd62e9dc964a9d17c7b89e", size = 241409, upload-time = "2026-06-27T08:15:05.439Z" }, + { url = "https://files.pythonhosted.org/packages/92/d1/8ce471f8d6752384f972fd5f6363f2e8d8b867a89fbd724c6dbd91d2bb98/xxhash-3.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:415a8d06ac9bea36b1e06b603a347e0f62401042a97d7bfccec8ae2da12ad784", size = 264433, upload-time = "2026-06-27T08:15:07.027Z" }, + { url = "https://files.pythonhosted.org/packages/95/77/400a281683fd39c54e2ac497fa67bdf886baaadb8c0ba58f7e1ea1d7692e/xxhash-3.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f5ccdd2deb5dce31201cc0eec94388cce97e681429073db50903fab0a0a8a0d", size = 242835, upload-time = "2026-06-27T08:15:08.703Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a6/edda651cfa0ba8e921791e93468fae655b63894d89730fcbfe46704f0d0a/xxhash-3.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1a6cf81bc699d3a5ebfcf2fdb2a7bd2e096708d7de193f6f322944a02ba00953", size = 473800, upload-time = "2026-06-27T08:15:10.503Z" }, + { url = "https://files.pythonhosted.org/packages/dd/da/50f764ec6a93d3961fce294567e41bfca0e66d168deed354a3dc90ebeba6/xxhash-3.8.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4d12a04d7ffc0359f0eadc4535a53cab113044c8d2f262c7e9a56950a5ed50e", size = 220677, upload-time = "2026-06-27T08:15:12.622Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/9fe4ed5aac6f38629cc83b34f84748b83ad8295a578ec6a49d8bf896cafb/xxhash-3.8.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d209373fcb66138c652cf843385ee60866e50158a7869bbbf8b322d9a822b765", size = 310385, upload-time = "2026-06-27T08:15:14.384Z" }, + { url = "https://files.pythonhosted.org/packages/83/f5/1147e03c0553ed22bbae9ce47503c37ee0c5f95592aae10f339c25f61de9/xxhash-3.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b88a3fe28277811e599efa6e1c96abce8a77d60dd79c94da7a9b5c377c172b7b", size = 238330, upload-time = "2026-06-27T08:15:16.201Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d8/92daf66c1966c84da5c97a06ced1480208d3a3bd465cb0630565ec00d1b9/xxhash-3.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5d5a888a5ef997cb35f1aad346eb861cd87ecfe24f5e25d5aa4c9fd1bd3950c2", size = 268667, upload-time = "2026-06-27T08:15:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c0/080c1a92972667e183c04b03f33c877f8ec61cfa3570e61731077286648d/xxhash-3.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:de2836e0329c01555957a603dcd113c337c577081153d691c12a51c5be3282b0", size = 224934, upload-time = "2026-06-27T08:15:19.972Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d5/cbc4e5b2bee10c94cba05b5bb2b8033e7ef44ae742583fdafcd9188e33ed/xxhash-3.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4bc74eedb0dd5827b3be748bacf9fdb50004037a3e16c7ddb5defae2682cef71", size = 240870, upload-time = "2026-06-27T08:15:22.04Z" }, + { url = "https://files.pythonhosted.org/packages/76/f7/09679b00e192b741b65c230440c4f7e6df3251a9ad427a518ddf262ec71a/xxhash-3.8.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:c571b03d59e339b010dc84f15a6f1cff80212f3a3116c2a71e2303c95065b1f6", size = 300683, upload-time = "2026-06-27T08:15:23.647Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1b/f43ec36e8c6a20c77be0bcca23f0b133ed8a0312681500d1676eebd71924/xxhash-3.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:87626acdd6e2d762c588a4ffe94258c5ef34fb6049a4a3b25019bdb7f9267a9b", size = 443407, upload-time = "2026-06-27T08:15:25.504Z" }, + { url = "https://files.pythonhosted.org/packages/45/2e/a3e3a779c5e4789daf975e05cc1c7f11bae724a03855120029d4592c8e63/xxhash-3.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:076d8a4fb290af952826922aa42a46bfc64caa31662ce4e2925a445d0e6ce57f", size = 217559, upload-time = "2026-06-27T08:15:27.234Z" }, + { url = "https://files.pythonhosted.org/packages/44/da/1c1e078ac290afff304a541a2a60965beb369ad65b4f30ec93ea1e0b7210/xxhash-3.8.0-cp314-cp314-win32.whl", hash = "sha256:52f8c7c9833d947e60df830671f6eca810d7c667051243985a561c79f1a3d545", size = 32602, upload-time = "2026-06-27T08:15:28.809Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7a/d455cb83d5e3c94046234294fb5dbbe5da600d1bbdf76b9527756920cce9/xxhash-3.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:4fbfcb7dd307e23189a71050f6e27746926590330f37d5fd2ffcb8ea78de1f42", size = 33393, upload-time = "2026-06-27T08:15:30.166Z" }, + { url = "https://files.pythonhosted.org/packages/89/8f/1b14471f617bc96edbb9566099a162d918a981381c398114726cc600b76c/xxhash-3.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:ecef1e65b4715c7326002073763fe94cc44c756a0698508abb915ab3d6be6e3d", size = 30007, upload-time = "2026-06-27T08:15:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/51ad2f9f784121c8057ef1ba36362f58d4595cbcad16322941f5b73eb53d/xxhash-3.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:02ed856a765cb6e006168595d9455ac8c3c4d60cc04cd47a158a1ac677d68f0f", size = 34957, upload-time = "2026-06-27T08:15:33.292Z" }, + { url = "https://files.pythonhosted.org/packages/1b/14/175c573ae4fac48bf21a82e5b9ceec75d64c520c51ca08de3105de539438/xxhash-3.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eec30461a7b457611098ba7ab09363e36c8b2645b4687fb6f3d405bb646e3410", size = 32635, upload-time = "2026-06-27T08:15:34.766Z" }, + { url = "https://files.pythonhosted.org/packages/96/08/f83efabd350a50c31c851b88891e318a6f07bdbf40a43d0f7bb6cedade7f/xxhash-3.8.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b471744912d1ce5dd6d3975b7525e77518359ebf3aa1bd7d501e199f5ae488ea", size = 225969, upload-time = "2026-06-27T08:15:36.35Z" }, + { url = "https://files.pythonhosted.org/packages/7c/78/2b6d12da9cf572c84d93b88ecbf9bf6539a7c5219bde128b214396b97c8b/xxhash-3.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3748d71202bf3f279e77cb8b273b6d0f29d1bcaefb6ce6cb03b95f358863ba37", size = 249851, upload-time = "2026-06-27T08:15:38.087Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0a/755eeb1882634983b24e6375a95ed233228dc48f0ef12655388bf3c7eeaf/xxhash-3.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b3bf59ea94b2a23b0f992769804ab9401d5cdcd9df0062fe2cd78a491ae8851", size = 274842, upload-time = "2026-06-27T08:15:39.808Z" }, + { url = "https://files.pythonhosted.org/packages/77/f2/09b1231cad17c314e51664c4a004c919108ec59aba10f9a28fa061e7b8be/xxhash-3.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:40f061aa5379eba249e9367b179515571e632be6d1b6f55ac139e6fe3d08463c", size = 252218, upload-time = "2026-06-27T08:15:42.105Z" }, + { url = "https://files.pythonhosted.org/packages/b2/24/de756d55547953494eb6775aea92e258035647b3ecb8547618cd549001e1/xxhash-3.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:680d70896a61fc920cc717a0a8fe8a9fb5858c563184666e31874caa54a16d9e", size = 482135, upload-time = "2026-06-27T08:15:44.476Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/b8147633e32f98ef2b4bb0dfca82f0f63e2b02ff179f20664af64c4216a7/xxhash-3.8.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14973fbdee136588e57447401b521f466a42faca41eecdf35123c73103512ca8", size = 226776, upload-time = "2026-06-27T08:15:46.597Z" }, + { url = "https://files.pythonhosted.org/packages/29/37/ba051d8f0380d3cf845b23ba058a17d32025846463eb6bf885887fc8effe/xxhash-3.8.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:96c6bca2486cdc58b125966817a92a6abe6ef1fab86b2f8798a7e93488782540", size = 319738, upload-time = "2026-06-27T08:15:48.394Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/36e0a27dd27ffa3f7b521650cbcd52a00fb86b71343ffadb642374e8263c/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0b1109ae238e932d8482f9cb568b56a405cc73bc7a36b837844087f1298dd218", size = 246136, upload-time = "2026-06-27T08:15:50.981Z" }, + { url = "https://files.pythonhosted.org/packages/fe/73/2663dbf4c09386a9dcc8a94d7a14b4609ed4bad8180ced5b848e60a9b660/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1da5db0863400eade7c5a31969754d1392189f26b4105f6631da2c6c7ea3bccc", size = 275568, upload-time = "2026-06-27T08:15:52.735Z" }, + { url = "https://files.pythonhosted.org/packages/d6/58/f3ce1bc3bb3971191f6521273ddae98d3c610bcefbbed5327c3b3627c12f/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c61b5a0f21ace5e886f177cce43826d85a7c84e35a9e17cb6d1b4ac0b7a7d833", size = 231314, upload-time = "2026-06-27T08:15:54.73Z" }, + { url = "https://files.pythonhosted.org/packages/4d/51/835706a36cdc00e5b638fba9b22218b3d40d23a7677c923feca8a3f55b98/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1db4f27835a450c7e729bc9330c6e702113711cea1f873d646e3a31fe96a9732", size = 250521, upload-time = "2026-06-27T08:15:56.853Z" }, + { url = "https://files.pythonhosted.org/packages/c1/47/b0b62caa3caee58ab9de8969f66aef1c3729886f3ff60e173fda3f2762be/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4788a470f946df34383abc6cd345088c13f897a5ee580c4cdd12b1d32ad218ef", size = 309926, upload-time = "2026-06-27T08:15:58.704Z" }, + { url = "https://files.pythonhosted.org/packages/69/c4/60e6d18a0e131c7af622374af9deede15d3c47d8e5e7221933481b57b319/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3b6dfa83096cb1e54d082acebaf67f0c42667c56dc48ba536a76cac08d46391e", size = 448812, upload-time = "2026-06-27T08:16:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/12/9f/c9627daa052be39a932d0e17c6bf6a9041d2cde3afacbded9196acf70261/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:57ec0ba5299a9a7df376063c139f5826ff0c89b438703939af3d252c31ca96a4", size = 223639, upload-time = "2026-06-27T08:16:02.784Z" }, + { url = "https://files.pythonhosted.org/packages/a9/38/92916e008a84c1f1a9aef82e4363cdc478a722ff69e59c6afbf93d3d1fda/xxhash-3.8.0-cp314-cp314t-win32.whl", hash = "sha256:d9a61f23b999baeb84102aba767b1b3e94958eab94e6c11b08927e7dc4200795", size = 33078, upload-time = "2026-06-27T08:16:04.639Z" }, + { url = "https://files.pythonhosted.org/packages/31/7c/e413bc75121d9628bf023b2ed251411ca3a447cf00cd9aa3438ab17f6c67/xxhash-3.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:61069b260fff84116235bb93845f319284dc6b42527c215af59264f4c2ee3468", size = 33953, upload-time = "2026-06-27T08:16:06.347Z" }, + { url = "https://files.pythonhosted.org/packages/f6/eb/21a96e218375bd8b6ecd6d07cf60c8ff1a046e93cdedc3cf7bc3309edf7b/xxhash-3.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:73cecd431b4f572d38fcf1a7fe85b30eb987778ef9e7a70bc9ffcf2d64810e6f", size = 30164, upload-time = "2026-06-27T08:16:08.009Z" }, + { url = "https://files.pythonhosted.org/packages/96/84/9bb3cc67475ac7678476b30eed2f1140431f06386d637534194037c0624f/xxhash-3.8.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ba14843f20df2dce6ff6684411a56ae53da44336546c55f8947e70aebb8cdd21", size = 32604, upload-time = "2026-06-27T08:17:19.291Z" }, + { url = "https://files.pythonhosted.org/packages/42/6d/e98f9dd62c89e8895e4f3b525b6dbc3efcf27e2b99800e51388c59eb96dd/xxhash-3.8.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ec6666a5311beae3f6cb5f2fd28c2b77e2df32702c8206f45c786a6ef81b3751", size = 29787, upload-time = "2026-06-27T08:17:21.001Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/e7844a65c62d6d78747e4d149508d65a3df6fb65d72322c2526789e9f600/xxhash-3.8.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1ec9afdd53ac5f4fd1d8918807ba6c35ba62269086af794884b9f168a73331ea", size = 43155, upload-time = "2026-06-27T08:17:22.721Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5d/652c47481053fabc33ea229540bd330a45f68d7a5277f45e6cf879c29965/xxhash-3.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68594a54be2eb5992d9b0d0a0ec7c32a7a8e930f06d6cb951d69708055680994", size = 38137, upload-time = "2026-06-27T08:17:24.295Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/7b6e961a03ee713cbdbaa3d2cf3ddd33453a4d4112bbde58f2f607ab64d2/xxhash-3.8.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:591d5eb256abf59438800ace2730ac33f77bc6ab8c3623fab1ea24d9d8b28f3a", size = 34376, upload-time = "2026-06-27T08:17:25.688Z" }, + { url = "https://files.pythonhosted.org/packages/da/aa/95d36393bf732df516a2dcf4fd7e9e851bc033a5970e30774b972137f4da/xxhash-3.8.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7f4eecf800275e62b6bcb41e65f361f2277cc886c2bff4e299959d701e5fcf93", size = 32798, upload-time = "2026-06-27T08:17:27.188Z" }, ] [[package]] From 4b86462690901b2103df0af9ec746fe327aa7f18 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 04:32:32 -0500 Subject: [PATCH 27/35] build: raise proven dev dependency floors --- packages/agentic-fabric/pyproject.toml | 18 ++++----- packages/pytest-agentic-fabric/pyproject.toml | 10 ++--- pyproject.toml | 10 ++--- tox.ini | 10 ++--- uv.lock | 38 +++++++++---------- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/packages/agentic-fabric/pyproject.toml b/packages/agentic-fabric/pyproject.toml index 30cd9c9..05c0d10 100644 --- a/packages/agentic-fabric/pyproject.toml +++ b/packages/agentic-fabric/pyproject.toml @@ -33,26 +33,26 @@ strands = ["strands-agents>=0.1.0"] mcp = ["mcp>=1.0.0"] scraping = ["beautifulsoup4>=4.12.0", "requests>=2.31.0"] tests = [ - "coverage[toml]>=7.6.0", - "pytest-asyncio>=0.23.0", + "coverage[toml]>=7.14.3", + "pytest-asyncio>=1.4.0", "pytest-cov>=7.1.0", "pytest-mock>=3.15.1", - "pytest>=9.0.3", + "pytest>=9.1.1", "pytest-timeout>=2.4.0", - "pytest-xdist>=3.6.1", + "pytest-xdist>=3.8.0", ] typing = [ - "mypy>=1.20.1", - "types-PyYAML>=6.0.12.20240724", - "types-requests>=2.33.0.20260408", + "mypy>=2.1.0", + "types-PyYAML>=6.0.12.20260518", + "types-requests>=2.33.0.20260518", ] dev = [ "agentic-fabric[tests,typing]", - "ruff>=0.8.0", + "ruff>=0.15.20", ] docs = [ "furo>=2025.12.19", - "myst-parser>=4.0.1,<6.0.0", + "myst-parser>=5.1.0,<6.0.0", "sphinx>=8.2.3,<9.0.0", "sphinx-autodoc2>=0.5.0", "sphinx-copybutton>=0.5.2", diff --git a/packages/pytest-agentic-fabric/pyproject.toml b/packages/pytest-agentic-fabric/pyproject.toml index 216c53c..1778ff7 100644 --- a/packages/pytest-agentic-fabric/pyproject.toml +++ b/packages/pytest-agentic-fabric/pyproject.toml @@ -23,21 +23,21 @@ classifiers = [ "Typing :: Typed", ] dependencies = [ - "pytest>=9.0.3", + "pytest>=9.1.1", "pyyaml>=6.0.3", ] [project.optional-dependencies] tests = [ - "pytest-asyncio>=0.23.0", - "pytest>=9.0.3", + "pytest-asyncio>=1.4.0", + "pytest>=9.1.1", ] typing = [ - "mypy>=1.20.1", + "mypy>=2.1.0", ] dev = [ "pytest-agentic-fabric[tests,typing]", - "ruff>=0.8.0", + "ruff>=0.15.20", ] [project.entry-points.pytest11] diff --git a/pyproject.toml b/pyproject.toml index 1858ed3..f7d8328 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,11 +19,11 @@ pytest-agentic-fabric = { workspace = true } [dependency-groups] dev = [ - "mypy>=1.20.1", - "ruff>=0.8.0", - "tox>=4.11", - "tox-gh>=1.3", - "tox-uv>=1.11", + "mypy>=2.1.0", + "ruff>=0.15.20", + "tox>=4.56.1", + "tox-gh>=1.7.1", + "tox-uv>=1.35.2", ] [tool.pytest.ini_options] diff --git a/tox.ini b/tox.ini index ef80847..3074b45 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,8 @@ [tox] requires = - tox>=4.11 - tox-uv>=1.11 - tox-gh>=1.3 + tox>=4.56.1 + tox-uv>=1.35.2 + tox-gh>=1.7.1 env_list = lint typecheck @@ -15,7 +15,7 @@ env_list = plugin docs build -min_version = 4.11 +min_version = 4.56.1 skip_missing_interpreters = false [gh] @@ -40,7 +40,7 @@ commands = {[testenv]commands} description = Lint source, tests, examples, docs, and workflow docs runner = uv-venv-runner package = skip -deps = ruff>=0.8.0 +deps = ruff>=0.15.20 commands = ruff check packages/agentic-fabric/src packages/agentic-fabric/tests packages/agentic-fabric/examples packages/pytest-agentic-fabric/src packages/pytest-agentic-fabric/tests docs AGENTS.md README.md [testenv:typecheck] diff --git a/uv.lock b/uv.lock index 7965e04..95e0731 100644 --- a/uv.lock +++ b/uv.lock @@ -90,28 +90,28 @@ typing = [ requires-dist = [ { name = "agentic-fabric", extras = ["tests", "typing"], marker = "extra == 'dev'", editable = "packages/agentic-fabric" }, { name = "beautifulsoup4", marker = "extra == 'scraping'", specifier = ">=4.12.0" }, - { name = "coverage", extras = ["toml"], marker = "extra == 'tests'", specifier = ">=7.6.0" }, + { name = "coverage", extras = ["toml"], marker = "extra == 'tests'", specifier = ">=7.14.3" }, { name = "furo", marker = "extra == 'docs'", specifier = ">=2025.12.19" }, { name = "langchain-anthropic", marker = "extra == 'langgraph'", specifier = ">=0.3.0" }, { name = "langgraph", marker = "extra == 'langgraph'", specifier = ">=0.2.0" }, { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.0.0" }, - { name = "mypy", marker = "extra == 'typing'", specifier = ">=1.20.1" }, - { name = "myst-parser", marker = "extra == 'docs'", specifier = ">=4.0.1,<6.0.0" }, - { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.0.3" }, - { name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=0.23.0" }, + { name = "mypy", marker = "extra == 'typing'", specifier = ">=2.1.0" }, + { name = "myst-parser", marker = "extra == 'docs'", specifier = ">=5.1.0,<6.0.0" }, + { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.1.1" }, + { name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=1.4.0" }, { name = "pytest-cov", marker = "extra == 'tests'", specifier = ">=7.1.0" }, { name = "pytest-mock", marker = "extra == 'tests'", specifier = ">=3.15.1" }, { name = "pytest-timeout", marker = "extra == 'tests'", specifier = ">=2.4.0" }, - { name = "pytest-xdist", marker = "extra == 'tests'", specifier = ">=3.6.1" }, + { name = "pytest-xdist", marker = "extra == 'tests'", specifier = ">=3.8.0" }, { name = "pyyaml", specifier = ">=6.0.3" }, { name = "requests", marker = "extra == 'scraping'", specifier = ">=2.31.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.20" }, { name = "sphinx", marker = "extra == 'docs'", specifier = ">=8.2.3,<9.0.0" }, { name = "sphinx-autodoc2", marker = "extra == 'docs'", specifier = ">=0.5.0" }, { name = "sphinx-copybutton", marker = "extra == 'docs'", specifier = ">=0.5.2" }, { name = "strands-agents", marker = "extra == 'strands'", specifier = ">=0.1.0" }, - { name = "types-pyyaml", marker = "extra == 'typing'", specifier = ">=6.0.12.20240724" }, - { name = "types-requests", marker = "extra == 'typing'", specifier = ">=2.33.0.20260408" }, + { name = "types-pyyaml", marker = "extra == 'typing'", specifier = ">=6.0.12.20260518" }, + { name = "types-requests", marker = "extra == 'typing'", specifier = ">=2.33.0.20260518" }, ] provides-extras = ["langgraph", "strands", "mcp", "scraping", "tests", "typing", "dev", "docs"] @@ -133,11 +133,11 @@ dev = [ [package.metadata.requires-dev] dev = [ - { name = "mypy", specifier = ">=1.20.1" }, - { name = "ruff", specifier = ">=0.8.0" }, - { name = "tox", specifier = ">=4.11" }, - { name = "tox-gh", specifier = ">=1.3" }, - { name = "tox-uv", specifier = ">=1.11" }, + { name = "mypy", specifier = ">=2.1.0" }, + { name = "ruff", specifier = ">=0.15.20" }, + { name = "tox", specifier = ">=4.56.1" }, + { name = "tox-gh", specifier = ">=1.7.1" }, + { name = "tox-uv", specifier = ">=1.35.2" }, ] [[package]] @@ -1792,13 +1792,13 @@ typing = [ [package.metadata] requires-dist = [ - { name = "mypy", marker = "extra == 'typing'", specifier = ">=1.20.1" }, - { name = "pytest", specifier = ">=9.0.3" }, - { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.0.3" }, + { name = "mypy", marker = "extra == 'typing'", specifier = ">=2.1.0" }, + { name = "pytest", specifier = ">=9.1.1" }, + { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.1.1" }, { name = "pytest-agentic-fabric", extras = ["tests", "typing"], marker = "extra == 'dev'", editable = "packages/pytest-agentic-fabric" }, - { name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=0.23.0" }, + { name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=1.4.0" }, { name = "pyyaml", specifier = ">=6.0.3" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.20" }, ] provides-extras = ["tests", "typing", "dev"] From a7f2f8194007d9b44829ec451b280445c4040403 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 04:36:53 -0500 Subject: [PATCH 28/35] ci: align automerge base context --- .github/workflows/automerge.yml | 13 +++-- .../tests/test_workflow_contracts.py | 50 +++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 packages/agentic-fabric/tests/test_workflow_contracts.py diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 6e12cf8..9e22edc 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -2,12 +2,17 @@ name: Automerge # Enables GitHub native auto-merge on dependabot + release-please PRs. # Native auto-merge waits for required checks + any required reviews, -# then squash-merges without further action. pull_request triggers fire -# from the target branch context, so secrets.CI_GITHUB_TOKEN is safe. +# then squash-merges without further action. This workflow does not check +# out PR code; pull_request_target is used only to enable auto-merge from +# the base workflow context. on: - pull_request: + pull_request_target: types: [opened, reopened, synchronize, ready_for_review] +permissions: + contents: write + pull-requests: write + jobs: automerge: name: Enable auto-merge @@ -21,6 +26,6 @@ jobs: steps: - name: Enable auto-merge (squash) env: - GH_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} PR_URL: ${{ github.event.pull_request.html_url }} run: gh pr merge --auto --squash "$PR_URL" diff --git a/packages/agentic-fabric/tests/test_workflow_contracts.py b/packages/agentic-fabric/tests/test_workflow_contracts.py new file mode 100644 index 0000000..33fc626 --- /dev/null +++ b/packages/agentic-fabric/tests/test_workflow_contracts.py @@ -0,0 +1,50 @@ +"""Tests for repository workflow contracts.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import yaml + + +WORKSPACE_ROOT = Path(__file__).resolve().parents[3] + + +def load_workflow(name: str) -> dict[str, Any]: + """Load a GitHub Actions workflow without YAML 1.1 boolean coercion issues.""" + return yaml.safe_load((WORKSPACE_ROOT / ".github" / "workflows" / name).read_text(encoding="utf-8")) + + +def test_automerge_uses_base_context_without_checkout_or_secrets() -> None: + """Automerge should match the split-package base-context workflow contract.""" + workflow = load_workflow("automerge.yml") + automerge = workflow["jobs"]["automerge"] + steps = automerge["steps"] + + assert workflow[True] == { + "pull_request_target": {"types": ["opened", "reopened", "synchronize", "ready_for_review"]} + } + assert workflow["permissions"] == {"contents": "write", "pull-requests": "write"} + assert "github-actions[bot]" in automerge["if"] + assert all("uses" not in step or "actions/checkout" not in step["uses"] for step in steps) + assert steps == [ + { + "name": "Enable auto-merge (squash)", + "env": { + "GH_TOKEN": "${{ github.token }}", + "PR_URL": "${{ github.event.pull_request.html_url }}", + }, + "run": 'gh pr merge --auto --squash "$PR_URL"', + } + ] + + +def test_ci_and_cd_quality_run_security_audit() -> None: + """CI and CD quality gates should include dependency auditing.""" + for workflow_name in ("ci.yml", "cd.yml"): + workflow = load_workflow(workflow_name) + quality_steps = workflow["jobs"]["quality"]["steps"] + tox_commands = [step["run"] for step in quality_steps if step.get("run", "").startswith("tox -e ")] + + assert tox_commands == ["tox -e lint,typecheck,audit,coverage,plugin,docs,build"] From 48f868e8ed56cd20fc69496c5c0368ffe4d27730 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 04:43:24 -0500 Subject: [PATCH 29/35] fix: await meshy mcp provider capabilities --- .../src/agentic_fabric/tools/meshy_mcp.py | 3 +++ .../agentic-fabric/tests/test_mcp_adapters.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/packages/agentic-fabric/src/agentic_fabric/tools/meshy_mcp.py b/packages/agentic-fabric/src/agentic_fabric/tools/meshy_mcp.py index 8a174a8..9523b01 100644 --- a/packages/agentic-fabric/src/agentic_fabric/tools/meshy_mcp.py +++ b/packages/agentic-fabric/src/agentic_fabric/tools/meshy_mcp.py @@ -7,6 +7,7 @@ from __future__ import annotations import asyncio +import inspect import json from collections.abc import Callable, Iterable, Mapping @@ -170,6 +171,8 @@ async def call_tool(name: str, arguments: dict[str, Any] | None) -> list[Any]: try: result = handler(**tool_arguments) + if inspect.isawaitable(result): + result = await result return [TextContent(type="text", text=_tool_result_text(result))] except Exception as exc: return [ diff --git a/packages/agentic-fabric/tests/test_mcp_adapters.py b/packages/agentic-fabric/tests/test_mcp_adapters.py index 2e2f76e..82b7b61 100644 --- a/packages/agentic-fabric/tests/test_mcp_adapters.py +++ b/packages/agentic-fabric/tests/test_mcp_adapters.py @@ -222,6 +222,25 @@ def explode(prompt: str) -> dict[str, str]: assert "failed cube" in result[0].text +def test_meshy_mcp_awaits_async_provider_capabilities(monkeypatch: pytest.MonkeyPatch) -> None: + install_fake_mcp(monkeypatch) + + async def generate(prompt: str) -> dict[str, str]: + return {"async": prompt} + + tools_module = types.ModuleType("vendor_fabric.meshy.tools") + tools_module.TOOL_DEFINITIONS = [{"name": "async_generate", "schema": None, "func": generate}] + monkeypatch.setitem(sys.modules, "vendor_fabric", types.ModuleType("vendor_fabric")) + monkeypatch.setitem(sys.modules, "vendor_fabric.meshy", types.ModuleType("vendor_fabric.meshy")) + monkeypatch.setitem(sys.modules, "vendor_fabric.meshy.tools", tools_module) + + server = meshy_mcp.create_server() + result = asyncio.run(server.call_tool_handler("async_generate", {"prompt": "cube"})) + + assert "cube" in result[0].text + assert "coroutine" not in result[0].text + + def test_meshy_mcp_helper_fallbacks(monkeypatch: pytest.MonkeyPatch) -> None: reject_imports(monkeypatch, "extended_data") From 3a5484014c3ade2b8711f40da3269f9e374c93e6 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 04:46:44 -0500 Subject: [PATCH 30/35] docs: make quick start fabric-first --- packages/agentic-fabric/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index 2fc6836..380930f 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -47,7 +47,7 @@ installs are unaffected. ### 1. Define a Fabric Agent (YAML) ```yaml -# .crewai/fabric_agents/analyzer/agents.yaml +# .fabric/fabric_agents/analyzer/agents.yaml code_reviewer: role: Senior Code Reviewer goal: Find bugs and improvements @@ -55,7 +55,7 @@ code_reviewer: ``` ```yaml -# .crewai/fabric_agents/analyzer/tasks.yaml +# .fabric/fabric_agents/analyzer/tasks.yaml review_code: description: Review the provided code for issues expected_output: List of findings with severity From 9e1badff90f521c5503688407883d889df0e6dbf Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 05:06:24 -0500 Subject: [PATCH 31/35] test: move fabric mockers into pytest package --- docs/architecture.rst | 5 +- docs/development.rst | 12 +- packages/agentic-fabric/pyproject.toml | 1 + .../src/agentic_fabric/base/archetypes.yaml | 2 +- .../src/agentic_fabric/core/discovery.py | 5 +- .../src/agentic_fabric/core/manager.py | 4 +- .../agentic-fabric/tests/_fabric_mocker.py | 509 ------------------ packages/agentic-fabric/tests/conftest.py | 183 ++----- packages/agentic-fabric/tests/e2e/conftest.py | 2 +- .../agentic-fabric/tests/test_cli_commands.py | 24 +- .../agentic-fabric/tests/test_cli_smoke.py | 2 +- .../agentic-fabric/tests/test_core_runner.py | 10 +- .../agentic-fabric/tests/test_discovery.py | 63 ++- .../tests/test_discovery_nested.py | 6 +- .../tests/test_fabric_mocker.py | 6 +- .../tests/test_import_surfaces.py | 3 +- packages/agentic-fabric/tests/test_loader.py | 2 +- packages/agentic-fabric/tests/test_manager.py | 26 +- packages/agentic-fabric/tests/test_runners.py | 2 +- packages/pytest-agentic-fabric/README.md | 16 + packages/pytest-agentic-fabric/pyproject.toml | 2 + .../src/pytest_agentic_fabric/__init__.py | 19 +- .../src/pytest_agentic_fabric/mocking.py | 343 ++++++++++++ .../src/pytest_agentic_fabric/plugin.py | 156 +++++- .../tests/test_plugin.py | 206 +++++++ tox.ini | 2 + uv.lock | 8 + 27 files changed, 890 insertions(+), 729 deletions(-) delete mode 100644 packages/agentic-fabric/tests/_fabric_mocker.py create mode 100644 packages/pytest-agentic-fabric/src/pytest_agentic_fabric/mocking.py diff --git a/docs/architecture.rst b/docs/architecture.rst index 705f49f..7d575d2 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -127,8 +127,9 @@ Testing Package --------------- ``pytest-agentic-fabric`` is a sibling package in this repository. It -provides runtime availability fixtures, runtime module mocking, minimal -fabric agent/workspace fixtures, and opt-in E2E controls. +provides runtime availability fixtures, framework mocking helpers, minimal +fabric agent/workspace fixtures, credential skip fixtures, and opt-in E2E +controls. Provider fixtures belong in ``pytest-vendor-fabric``. Base data fixtures belong in ``pytest-extended-data``. diff --git a/docs/development.rst b/docs/development.rst index c62e626..0ab392b 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -23,8 +23,8 @@ Use ``pytest-agentic-fabric`` fixtures for reusable runtime test setup: .. code:: python - def test_runtime(agentic_mock_runtime, agentic_fabric_agent_config): - agentic_mock_runtime("langgraph") + def test_runtime(agentic_fabric_mocker, agentic_fabric_agent_config): + agentic_fabric_mocker.mock_langgraph() assert agentic_fabric_agent_config["name"] == "test_fabric_agent" Override ``agentic_fabric_agent_config`` when a test needs a custom @@ -32,5 +32,13 @@ discoverable workspace. The ``agentic_workspace`` fixture writes that config into a temporary ``packages/sample/.fabric`` tree with matching manifest, agents, and tasks YAML files. +Published helper fixtures include ``fabric_mocker``/``agentic_fabric_mocker``, +``mock_agentic_frameworks``, ``mock_crewai``, ``mock_langgraph``, +``mock_strands``, ``simple_agent_config``, ``simple_task_config``, +``simple_fabric_agent_config``, ``multi_agent_fabric_agent_config``, +``fabric_agent_with_knowledge``, and ``temp_fabric_dir``. Use those fixtures +instead of private test modules so downstream packages exercise the same +pytest plugin surface that is published with the workspace. + Before deleting old monorepo code, first prove this repository owns the moved surface with docs, tests, package metadata, and release workflows. diff --git a/packages/agentic-fabric/pyproject.toml b/packages/agentic-fabric/pyproject.toml index 05c0d10..c46b787 100644 --- a/packages/agentic-fabric/pyproject.toml +++ b/packages/agentic-fabric/pyproject.toml @@ -34,6 +34,7 @@ mcp = ["mcp>=1.0.0"] scraping = ["beautifulsoup4>=4.12.0", "requests>=2.31.0"] tests = [ "coverage[toml]>=7.14.3", + "pytest-agentic-fabric[tests]", "pytest-asyncio>=1.4.0", "pytest-cov>=7.1.0", "pytest-mock>=3.15.1", diff --git a/packages/agentic-fabric/src/agentic_fabric/base/archetypes.yaml b/packages/agentic-fabric/src/agentic_fabric/base/archetypes.yaml index 5a114c2..8278632 100644 --- a/packages/agentic-fabric/src/agentic_fabric/base/archetypes.yaml +++ b/packages/agentic-fabric/src/agentic_fabric/base/archetypes.yaml @@ -3,7 +3,7 @@ # These are base templates that packages can extend in their own agents.yaml. # Use the 'extends' field to inherit from an archetype. # -# Example usage in packages/mypackage/.crewai/fabric_agents/builder/agents.yaml: +# Example usage in packages/mypackage/.fabric/fabric_agents/builder/agents.yaml: # my_engineer: # extends: senior_engineer # variables: diff --git a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py index 497ea0c..7cf534d 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/discovery.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/discovery.py @@ -2,11 +2,12 @@ Supports framework-specific configuration directories: - .fabric/ - framework-agnostic fabric configuration -- .crewai/ - CrewAI-specific configurations (default) +- .crewai/ - CrewAI-specific configurations - .langgraph/ - LangGraph-specific configurations - .strands/ - Strands-specific configurations -The discovery order matches framework priority for auto-detection. +The discovery order prefers the agnostic fabric before runtime-specific +directories. """ from __future__ import annotations diff --git a/packages/agentic-fabric/src/agentic_fabric/core/manager.py b/packages/agentic-fabric/src/agentic_fabric/core/manager.py index 84f3e1b..240f35f 100644 --- a/packages/agentic-fabric/src/agentic_fabric/core/manager.py +++ b/packages/agentic-fabric/src/agentic_fabric/core/manager.py @@ -131,8 +131,8 @@ def delegate( packages = self._get_packages() if self.package_name not in packages: raise ValueError(f"Package '{self.package_name}' not found. Available: {list(packages.keys())}") - crewai_dir = packages[self.package_name] - fabric_agent_config = get_fabric_agent_config(crewai_dir, fabric_agent_name) + config_dir = packages[self.package_name] + fabric_agent_config = get_fabric_agent_config(config_dir, fabric_agent_name) else: # Auto-discover package containing the fabric agent packages = self._get_packages() diff --git a/packages/agentic-fabric/tests/_fabric_mocker.py b/packages/agentic-fabric/tests/_fabric_mocker.py deleted file mode 100644 index be72a64..0000000 --- a/packages/agentic-fabric/tests/_fabric_mocker.py +++ /dev/null @@ -1,509 +0,0 @@ -"""Mocking utilities for agentic-fabric testing. - -This module provides specialized mocking fixtures that build on top of pytest-mock -to make it easy to test agentic-fabric components without needing the actual -AI frameworks installed. - -The fixtures automatically mock framework modules and provide convenient methods -for patching agentic-fabric internals. -""" - -from __future__ import annotations - -import sys - -from collections.abc import Generator -from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any - -import pytest - - -if TYPE_CHECKING: - from pytest_mock import MockerFixture - - -# ============================================================================= -# Framework Module Lists -# ============================================================================= - -CREWAI_MODULES = [ - "crewai", - "crewai.knowledge", - "crewai.knowledge.source", - "crewai.knowledge.source.text_file_knowledge_source", -] - -LANGGRAPH_MODULES = [ - "langgraph", - "langgraph.prebuilt", - "langchain_anthropic", -] - -STRANDS_MODULES = [ - "strands", -] - -ALL_FRAMEWORK_MODULES = CREWAI_MODULES + LANGGRAPH_MODULES + STRANDS_MODULES - - -# ============================================================================= -# FabricMocker Class - Enhanced mocker for agentic-fabric testing -# ============================================================================= - - -@dataclass -class FabricMocker: - """Enhanced mocker specifically for agentic-fabric testing. - - This class wraps pytest-mock's MockerFixture and provides convenience - methods for mocking AI framework components commonly used in agentic-fabric. - - Attributes: - mocker: The underlying pytest-mock MockerFixture. - mocked_modules: Dictionary of currently mocked framework modules. - - Example: - def test_crewai_runner(fabric_mocker): - # Mock CrewAI components - mock_agent = fabric_mocker.mock_crewai_agent() - mock_crew = fabric_mocker.mock_crewai_crew() - - # Test runner behavior - from agentic_fabric.runners.crewai_runner import CrewAIRunner - runner = CrewAIRunner() - ... - """ - - mocker: MockerFixture - mocked_modules: dict[str, Any] = field(default_factory=dict) - _original_modules: dict[str, Any] = field(default_factory=dict) - - # Expose common pytest-mock attributes - @property - def MagicMock(self) -> Any: - """Access to mocker.MagicMock.""" - return self.mocker.MagicMock - - @property - def Mock(self) -> Any: - """Access to mocker.Mock.""" - return self.mocker.Mock - - @property - def patch(self) -> Any: - """Access to mocker.patch.""" - return self.mocker.patch - - @property - def spy(self) -> Any: - """Access to mocker.spy.""" - return self.mocker.spy - - @property - def stub(self) -> Any: - """Access to mocker.stub.""" - return self.mocker.stub - - def mock_module(self, module_name: str) -> Any: - """Mock a single module in sys.modules. - - Args: - module_name: Full module path (e.g., 'crewai.Agent'). - - Returns: - The mock object for the module. - """ - if module_name in self.mocked_modules: - return self.mocked_modules[module_name] - - mock = self.mocker.MagicMock() - if module_name in sys.modules: - self._original_modules[module_name] = sys.modules[module_name] - sys.modules[module_name] = mock - self.mocked_modules[module_name] = mock - return mock - - def mock_modules(self, module_names: list[str]) -> dict[str, Any]: - """Mock multiple modules in sys.modules. - - Args: - module_names: List of module paths to mock. - - Returns: - Dictionary mapping module names to their mock objects. - """ - return {name: self.mock_module(name) for name in module_names} - - def restore_modules(self) -> None: - """Restore all mocked modules to their original state.""" - for module_name in list(self.mocked_modules.keys()): - if module_name in self._original_modules: - sys.modules[module_name] = self._original_modules[module_name] - else: - sys.modules.pop(module_name, None) - self.mocked_modules.clear() - self._original_modules.clear() - - # ========================================================================= - # CrewAI Mocking Helpers - # ========================================================================= - - def mock_crewai(self) -> dict[str, Any]: - """Mock all CrewAI framework modules. - - Returns: - Dictionary of mocked CrewAI modules. - """ - mocks = self.mock_modules(CREWAI_MODULES) - # Set up nested module attributes - if "crewai.knowledge.source.text_file_knowledge_source" in mocks: - mocks[ - "crewai.knowledge.source.text_file_knowledge_source" - ].TextFileKnowledgeSource = self.mocker.MagicMock() - return mocks - - def mock_crewai_agent(self, **kwargs: Any) -> Any: - """Create a mock for crewai.Agent. - - Args: - **kwargs: Additional attributes to set on the mock. - - Returns: - Mock Agent object. - """ - mock_agent = self.mocker.MagicMock() - for key, value in kwargs.items(): - setattr(mock_agent, key, value) - return mock_agent - - def mock_crewai_task(self, **kwargs: Any) -> Any: - """Create a mock for crewai.Task. - - Args: - **kwargs: Additional attributes to set on the mock. - - Returns: - Mock Task object. - """ - mock_task = self.mocker.MagicMock() - for key, value in kwargs.items(): - setattr(mock_task, key, value) - return mock_task - - def mock_crewai_crew(self, result: str = "Test result", **kwargs: Any) -> Any: - """Create a mock for crewai.Crew with kickoff behavior. - - Args: - result: The result to return from kickoff(). - **kwargs: Additional attributes to set on the mock. - - Returns: - Mock Crew object with kickoff configured. - """ - mock_crew = self.mocker.MagicMock() - mock_result = self.mocker.MagicMock() - mock_result.raw = result - mock_crew.kickoff.return_value = mock_result - for key, value in kwargs.items(): - setattr(mock_crew, key, value) - return mock_crew - - def patch_crewai_agent(self) -> Any: - """Patch crewai.Agent and return the mock. - - Returns: - The mock for crewai.Agent. - """ - return self.mocker.patch("crewai.Agent") - - def patch_crewai_task(self) -> Any: - """Patch crewai.Task and return the mock. - - Returns: - The mock for crewai.Task. - """ - return self.mocker.patch("crewai.Task") - - def patch_crewai_crew(self) -> Any: - """Patch crewai.Crew and return the mock. - - Returns: - The mock for crewai.Crew. - """ - return self.mocker.patch("crewai.Crew") - - def patch_crewai_process(self) -> Any: - """Patch crewai.Process and return the mock. - - Returns: - The mock for crewai.Process. - """ - return self.mocker.patch("crewai.Process") - - def patch_knowledge_source(self) -> Any: - """Get the TextFileKnowledgeSource mock from the mocked crewai module. - - Note: Must call mock_crewai() first. - - Returns: - The mock for TextFileKnowledgeSource. - """ - # If the module is already mocked, return its TextFileKnowledgeSource - if "crewai.knowledge.source.text_file_knowledge_source" in self.mocked_modules: - return self.mocked_modules["crewai.knowledge.source.text_file_knowledge_source"].TextFileKnowledgeSource - # Otherwise, fall back to patching - return self.mocker.patch("crewai.knowledge.source.text_file_knowledge_source.TextFileKnowledgeSource") - - # ========================================================================= - # LangGraph Mocking Helpers - # ========================================================================= - - def mock_langgraph(self) -> dict[str, Any]: - """Mock all LangGraph framework modules. - - Returns: - Dictionary of mocked LangGraph modules. - """ - mocks = self.mock_modules(LANGGRAPH_MODULES) - # Set up the prebuilt module with create_react_agent - if "langgraph.prebuilt" in mocks: - mocks["langgraph.prebuilt"].create_react_agent = self.mocker.MagicMock() - return mocks - - def patch_create_react_agent(self) -> Any: - """Get the create_react_agent mock from the mocked langgraph module. - - Note: Must call mock_langgraph() first. - - Returns: - The mock for create_react_agent. - """ - # If langgraph.prebuilt is already mocked, return its create_react_agent - if "langgraph.prebuilt" in self.mocked_modules: - return self.mocked_modules["langgraph.prebuilt"].create_react_agent - # Otherwise, fall back to patching - return self.mocker.patch("langgraph.prebuilt.create_react_agent") - - def patch_chat_anthropic(self) -> Any: - """Get the ChatAnthropic mock from the mocked langchain_anthropic module. - - Note: Must call mock_langgraph() first. - - Returns: - The mock for ChatAnthropic. - """ - # If langchain_anthropic is already mocked, return its ChatAnthropic - if "langchain_anthropic" in self.mocked_modules: - return self.mocked_modules["langchain_anthropic"].ChatAnthropic - # Otherwise, fall back to patching - return self.mocker.patch("langchain_anthropic.ChatAnthropic") - - def mock_langgraph_graph(self, result: str = "Test response") -> Any: - """Create a mock LangGraph graph with invoke behavior. - - Args: - result: The result content to return from invoke(). - - Returns: - Mock graph object with invoke configured. - """ - mock_graph = self.mocker.MagicMock() - mock_message = self.mocker.MagicMock() - mock_message.content = result - mock_graph.invoke.return_value = {"messages": [mock_message]} - return mock_graph - - # ========================================================================= - # Strands Mocking Helpers - # ========================================================================= - - def mock_strands(self) -> dict[str, Any]: - """Mock all Strands framework modules. - - Returns: - Dictionary of mocked Strands modules. - """ - return self.mock_modules(STRANDS_MODULES) - - def patch_strands_agent(self) -> Any: - """Patch strands.Agent and return the mock. - - Returns: - The mock for strands.Agent. - """ - return self.mocker.patch("strands.Agent") - - def mock_strands_agent(self, result: str = "Test response") -> Any: - """Create a mock Strands agent with call behavior. - - Args: - result: The result to return when agent is called. - - Returns: - Mock agent object. - """ - mock_agent = self.mocker.MagicMock() - mock_agent.return_value = result - return mock_agent - - # ========================================================================= - # All Frameworks - # ========================================================================= - - def mock_all_frameworks(self) -> dict[str, Any]: - """Mock all AI framework modules (CrewAI, LangGraph, Strands). - - Returns: - Dictionary of all mocked framework modules. - """ - return self.mock_modules(ALL_FRAMEWORK_MODULES) - - # ========================================================================= - # agentic-fabric Internal Mocking - # ========================================================================= - - def patch_get_llm(self, return_value: Any = None) -> Any: - """Patch agentic_fabric.config.llm.get_llm. - - Args: - return_value: Value to return from get_llm. If None, creates a MagicMock. - - Returns: - The mock for get_llm. - """ - mock = self.mocker.patch("agentic_fabric.config.llm.get_llm") - mock.return_value = return_value or self.mocker.MagicMock() - return mock - - def patch_discover_packages(self, packages: dict[str, Any] | None = None) -> Any: - """Patch agentic_fabric.core.discovery.discover_packages. - - Args: - packages: Dictionary of packages to return. If None, returns empty dict. - - Returns: - The mock for discover_packages. - """ - mock = self.mocker.patch("agentic_fabric.core.discovery.discover_packages") - mock.return_value = packages or {} - return mock - - def patch_get_fabric_agent_config(self, config: dict[str, Any] | None = None) -> Any: - """Patch agentic_fabric.core.discovery.get_fabric_agent_config. - - Args: - config: Config to return. If None, returns minimal valid config. - - Returns: - The mock for get_fabric_agent_config. - """ - mock = self.mocker.patch("agentic_fabric.core.discovery.get_fabric_agent_config") - mock.return_value = {"name": "test", "agents": {}, "tasks": {}} if config is None else config - return mock - - def patch_run_fabric_agent_auto(self, result: str = "Test result") -> Any: - """Patch agentic_fabric.core.decomposer.run_fabric_agent_auto. - - Args: - result: Result string to return from run_fabric_agent_auto. - - Returns: - The mock for run_fabric_agent_auto. - """ - mock = self.mocker.patch("agentic_fabric.core.decomposer.run_fabric_agent_auto") - mock.return_value = result - return mock - - -# ============================================================================= -# Fixtures -# ============================================================================= - - -@pytest.fixture -def fabric_mocker(mocker: MockerFixture) -> Generator[FabricMocker, None, None]: - """Fixture providing FabricMocker for agentic-fabric testing. - - This fixture builds on pytest-mock's mocker fixture and provides - specialized methods for mocking AI framework components. - - Yields: - FabricMocker instance with access to all mocking utilities. - - Example: - def test_my_runner(fabric_mocker): - # Mock CrewAI - fabric_mocker.mock_crewai() - mock_llm = fabric_mocker.patch_get_llm() - - # Test code - from agentic_fabric.runners.crewai_runner import CrewAIRunner - runner = CrewAIRunner() - ... - """ - cm = FabricMocker(mocker=mocker) - yield cm - cm.restore_modules() - - -@pytest.fixture -def mock_frameworks(mocker: MockerFixture) -> Generator[dict[str, Any], None, None]: - """Fixture to mock all AI framework modules for unit testing. - - This is a convenience fixture that mocks all framework modules - (crewai, langgraph, strands) so tests can run without the actual - frameworks installed. - - Yields: - Dictionary mapping module names to their mock objects. - - Example: - def test_crewai_runner(mock_frameworks): - from agentic_fabric.runners.crewai_runner import CrewAIRunner - runner = CrewAIRunner() - # Test runner behavior with mocked framework - """ - cm = FabricMocker(mocker=mocker) - mocks = cm.mock_all_frameworks() - yield mocks - cm.restore_modules() - - -@pytest.fixture -def mock_crewai(mocker: MockerFixture) -> Generator[dict[str, Any], None, None]: - """Fixture to mock only CrewAI framework modules. - - Yields: - Dictionary with CrewAI mock objects. - """ - cm = FabricMocker(mocker=mocker) - mocks = cm.mock_crewai() - yield mocks - cm.restore_modules() - - -@pytest.fixture -def mock_langgraph(mocker: MockerFixture) -> Generator[dict[str, Any], None, None]: - """Fixture to mock only LangGraph framework modules. - - Yields: - Dictionary with LangGraph mock objects. - """ - cm = FabricMocker(mocker=mocker) - mocks = cm.mock_langgraph() - yield mocks - cm.restore_modules() - - -@pytest.fixture -def mock_strands(mocker: MockerFixture) -> Generator[dict[str, Any], None, None]: - """Fixture to mock only Strands framework modules. - - Yields: - Dictionary with Strands mock objects. - """ - cm = FabricMocker(mocker=mocker) - mocks = cm.mock_strands() - yield mocks - cm.restore_modules() diff --git a/packages/agentic-fabric/tests/conftest.py b/packages/agentic-fabric/tests/conftest.py index 5e5ca80..1c544bb 100644 --- a/packages/agentic-fabric/tests/conftest.py +++ b/packages/agentic-fabric/tests/conftest.py @@ -11,7 +11,23 @@ import pytest -from tests._fabric_mocker import fabric_mocker, mock_crewai, mock_frameworks, mock_langgraph, mock_strands # noqa: F401 +from pytest_agentic_fabric.mocking import ( # noqa: F401 + fabric_mocker, + mock_agentic_frameworks, + mock_crewai, + mock_langgraph, + mock_strands, +) +from pytest_agentic_fabric.plugin import ( # noqa: F401 + check_api_key, + check_aws_credentials, + fabric_agent_with_knowledge, + multi_agent_fabric_agent_config, + simple_agent_config, + simple_fabric_agent_config, + simple_task_config, + temp_fabric_dir, +) def pytest_addoption(parser: Any) -> None: @@ -76,16 +92,17 @@ def temp_workspace(tmp_path: Path) -> Path: packages_dir = tmp_path / "packages" packages_dir.mkdir() - # Create a mock sample package with .crewai structure + # Create a mock sample package with framework-agnostic .fabric structure sample_dir = packages_dir / "sample" sample_dir.mkdir() - crewai_dir = sample_dir / ".crewai" - crewai_dir.mkdir() + fabric_dir = sample_dir / ".fabric" + fabric_dir.mkdir() # Create minimal manifest (dict format, not list) - manifest = crewai_dir / "manifest.yaml" - manifest.write_text(""" + manifest = fabric_dir / "manifest.yaml" + manifest.write_text( + """ name: sample description: Test package fabric_agents: @@ -93,26 +110,34 @@ def temp_workspace(tmp_path: Path) -> Path: description: A test fabric agent agents: fabric_agents/test_fabric_agent/agents.yaml tasks: fabric_agents/test_fabric_agent/tasks.yaml -""") +""", + encoding="utf-8", + ) # Create fabric_agents directory - fabric_agents_dir = crewai_dir / "fabric_agents" / "test_fabric_agent" + fabric_agents_dir = fabric_dir / "fabric_agents" / "test_fabric_agent" fabric_agents_dir.mkdir(parents=True) # Create minimal agent and task configs - (fabric_agents_dir / "agents.yaml").write_text(""" + (fabric_agents_dir / "agents.yaml").write_text( + """ test_agent: role: Test Agent goal: Test goal backstory: Test backstory -""") +""", + encoding="utf-8", + ) - (fabric_agents_dir / "tasks.yaml").write_text(""" + (fabric_agents_dir / "tasks.yaml").write_text( + """ test_task: description: Test task description expected_output: Test output agent: test_agent -""") +""", + encoding="utf-8", + ) return tmp_path @@ -123,137 +148,3 @@ def mock_fabric_agent_result() -> MagicMock: result = MagicMock() result.raw = {"output": "test output", "success": True} return result - - -@pytest.fixture -def check_api_key() -> None: - """Skip live E2E tests when Anthropic credentials are unavailable.""" - if not os.environ.get("ANTHROPIC_API_KEY"): - pytest.skip("ANTHROPIC_API_KEY not set") - - -@pytest.fixture -def check_aws_credentials() -> None: - """Skip live E2E tests when AWS Bedrock credentials are unavailable.""" - has_key = os.environ.get("AWS_ACCESS_KEY_ID") - has_profile = os.environ.get("AWS_PROFILE") - if not (has_key or has_profile): - pytest.skip("AWS credentials not configured; set AWS_ACCESS_KEY_ID or AWS_PROFILE") - - -@pytest.fixture -def temp_crewai_dir(tmp_path: Path) -> Path: - """Create a temporary CrewAI config directory with minimal structure.""" - crewai_dir = tmp_path / "test_package" / ".crewai" - crewai_dir.mkdir(parents=True) - (crewai_dir / "fabric_agents").mkdir() - return crewai_dir - - -@pytest.fixture -def simple_agent_config() -> dict[str, Any]: - """Get a simple agent configuration for runner tests.""" - return { - "role": "Test Agent", - "goal": "Answer simple questions accurately", - "backstory": "You are a helpful assistant focused on providing clear, concise answers.", - } - - -@pytest.fixture -def simple_task_config() -> dict[str, Any]: - """Get a simple task configuration for runner tests.""" - return { - "description": "Answer the question: What is 2 + 2?", - "expected_output": "The answer to the mathematical question", - } - - -@pytest.fixture -def simple_fabric_agent_config( - simple_agent_config: dict[str, Any], - simple_task_config: dict[str, Any], -) -> dict[str, Any]: - """Get a simple fabric agent configuration for runner tests.""" - return { - "name": "test_fabric_agent", - "description": "A simple test fabric agent", - "agents": {"test_agent": simple_agent_config}, - "tasks": { - "test_task": { - **simple_task_config, - "agent": "test_agent", - }, - }, - "knowledge_paths": [], - } - - -@pytest.fixture -def multi_agent_fabric_agent_config() -> dict[str, Any]: - """Get a multi-agent fabric agent configuration for runner tests.""" - return { - "name": "multi_agent_fabric_agent", - "description": "A fabric agent with multiple collaborating agents", - "agents": { - "researcher": { - "role": "Researcher", - "goal": "Gather and analyze information", - "backstory": "You are an expert researcher.", - }, - "writer": { - "role": "Writer", - "goal": "Write clear summaries", - "backstory": "You are a skilled technical writer.", - }, - }, - "tasks": { - "research_task": { - "description": "Research the topic: Python programming", - "expected_output": "Key facts about Python", - "agent": "researcher", - }, - "writing_task": { - "description": "Write a brief summary based on the research", - "expected_output": "A concise summary", - "agent": "writer", - "context": ["research_task"], - }, - }, - "knowledge_paths": [], - } - - -@pytest.fixture -def fabric_agent_with_knowledge(tmp_path: Path) -> dict[str, Any]: - """Get a fabric agent configuration with a local knowledge source.""" - knowledge_dir = tmp_path / "knowledge" - knowledge_dir.mkdir() - (knowledge_dir / "test_info.md").write_text( - """# Test Knowledge - -This is test knowledge about the color blue. -Blue is a primary color. -It is often associated with calmness and stability. -""", - ) - - return { - "name": "knowledge_fabric_agent", - "description": "A fabric agent with knowledge sources", - "agents": { - "knowledgeable_agent": { - "role": "Knowledge Expert", - "goal": "Answer questions using provided knowledge", - "backstory": "You have access to specialized knowledge.", - }, - }, - "tasks": { - "knowledge_task": { - "description": "What color is mentioned in the knowledge base?", - "expected_output": "The color mentioned", - "agent": "knowledgeable_agent", - }, - }, - "knowledge_paths": [knowledge_dir], - } diff --git a/packages/agentic-fabric/tests/e2e/conftest.py b/packages/agentic-fabric/tests/e2e/conftest.py index 0014ee6..d804e5d 100644 --- a/packages/agentic-fabric/tests/e2e/conftest.py +++ b/packages/agentic-fabric/tests/e2e/conftest.py @@ -14,7 +14,7 @@ # - simple_fabric_agent_config # - multi_agent_fabric_agent_config # - fabric_agent_with_knowledge -# - temp_crewai_dir +# - temp_fabric_dir # # CLI options: # - --e2e: Enable E2E tests diff --git a/packages/agentic-fabric/tests/test_cli_commands.py b/packages/agentic-fabric/tests/test_cli_commands.py index 093e71b..4da9464 100644 --- a/packages/agentic-fabric/tests/test_cli_commands.py +++ b/packages/agentic-fabric/tests/test_cli_commands.py @@ -92,7 +92,7 @@ def test_cmd_run_json_success_auto_detects_framework( ) with ( - patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".fabric"}), patch.object(cli, "get_fabric_agent_config", return_value={"name": "reviewer"}), patch("agentic_fabric.core.decomposer.detect_framework", return_value="crewai"), patch("agentic_fabric.core.decomposer.run_fabric_agent_auto", return_value="done") as run_fabric_agent_auto, @@ -163,7 +163,7 @@ def test_cmd_run_json_reports_missing_package(capsys: pytest.CaptureFixture[str] ) with ( - patch.object(cli, "discover_packages", return_value={"known": Path(".crewai")}), + patch.object(cli, "discover_packages", return_value={"known": Path(".fabric")}), pytest.raises(SystemExit) as exc_info, ): cli.cmd_run(args) @@ -189,7 +189,7 @@ def test_cmd_run_json_reports_execution_errors( ) with ( - patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".fabric"}), patch.object(cli, "get_fabric_agent_config", side_effect=ValueError("bad fabric agent")), pytest.raises(SystemExit) as exc_info, ): @@ -255,7 +255,7 @@ def test_cmd_run_text_reports_missing_package(capsys: pytest.CaptureFixture[str] ) with ( - patch.object(cli, "discover_packages", return_value={"known": Path(".crewai")}), + patch.object(cli, "discover_packages", return_value={"known": Path(".fabric")}), pytest.raises(SystemExit) as exc_info, ): cli.cmd_run(args) @@ -321,7 +321,7 @@ def test_cmd_run_text_success_and_error_paths(tmp_path: Path, capsys: pytest.Cap ) with ( - patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".fabric"}), patch.object(cli, "get_fabric_agent_config", return_value={"name": "reviewer", "required_framework": "crewai"}), patch("agentic_fabric.core.decomposer.run_fabric_agent_auto", return_value="done"), ): @@ -333,7 +333,7 @@ def test_cmd_run_text_success_and_error_paths(tmp_path: Path, capsys: pytest.Cap assert "done" in output with ( - patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".fabric"}), patch.object(cli, "get_fabric_agent_config", side_effect=RuntimeError("boom")), pytest.raises(SystemExit) as exc_info, ): @@ -354,7 +354,7 @@ def test_cmd_info_json_success(tmp_path: Path, capsys: pytest.CaptureFixture[str } with ( - patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".fabric"}), patch.object(cli, "get_fabric_agent_config", return_value=config), ): cli.cmd_info(args) @@ -370,7 +370,7 @@ def test_cmd_info_json_reports_config_error(tmp_path: Path, capsys: pytest.Captu args = SimpleNamespace(package="pkg", fabric_agent="missing", json=True) with ( - patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".fabric"}), patch.object(cli, "get_fabric_agent_config", side_effect=ValueError("missing fabric agent")), pytest.raises(SystemExit) as exc_info, ): @@ -384,7 +384,7 @@ def test_cmd_info_json_reports_missing_package(capsys: pytest.CaptureFixture[str args = SimpleNamespace(package="missing", fabric_agent="reviewer", json=True) with ( - patch.object(cli, "discover_packages", return_value={"known": Path(".crewai")}), + patch.object(cli, "discover_packages", return_value={"known": Path(".fabric")}), pytest.raises(SystemExit) as exc_info, ): cli.cmd_info(args) @@ -405,7 +405,7 @@ def test_cmd_info_text_success_and_errors(tmp_path: Path, capsys: pytest.Capture } with ( - patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".fabric"}), patch.object(cli, "get_fabric_agent_config", return_value=config), ): cli.cmd_info(args) @@ -425,7 +425,7 @@ def test_cmd_info_text_success_and_errors(tmp_path: Path, capsys: pytest.Capture assert "Package 'pkg' not found" in capsys.readouterr().out with ( - patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".crewai"}), + patch.object(cli, "discover_packages", return_value={"pkg": tmp_path / ".fabric"}), patch.object(cli, "get_fabric_agent_config", side_effect=ValueError("missing fabric agent")), pytest.raises(SystemExit) as exc_info, ): @@ -508,7 +508,7 @@ def test_single_agent_json_success_uses_package_workdir( capsys: pytest.CaptureFixture[str], ) -> None: fake_runner = FakeCliRunner(result="fixed") - package_dir = tmp_path / "pkg" / ".crewai" + package_dir = tmp_path / "pkg" / ".fabric" args = SimpleNamespace( json=True, diff --git a/packages/agentic-fabric/tests/test_cli_smoke.py b/packages/agentic-fabric/tests/test_cli_smoke.py index 57147a6..0619256 100644 --- a/packages/agentic-fabric/tests/test_cli_smoke.py +++ b/packages/agentic-fabric/tests/test_cli_smoke.py @@ -95,7 +95,7 @@ def test_info_nonexistent_package_exits_2(self) -> None: def test_info_missing_fabric_agent_exits_2(self, tmp_path) -> None: """'info' with non-existent fabric agent should exit with code 2.""" - config_dir = tmp_path / ".crewai" + config_dir = tmp_path / ".fabric" config_dir.mkdir() (config_dir / "manifest.yaml").write_text("fabric_agents: {}\n") diff --git a/packages/agentic-fabric/tests/test_core_runner.py b/packages/agentic-fabric/tests/test_core_runner.py index 4e6c1b0..8b0f0fc 100644 --- a/packages/agentic-fabric/tests/test_core_runner.py +++ b/packages/agentic-fabric/tests/test_core_runner.py @@ -36,11 +36,11 @@ def kickoff(self, inputs: dict[str, Any]) -> Any: def test_run_fabric_agent_discovers_package_loads_config_and_returns_raw(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - crewai_dir = tmp_path / "pkg" / ".crewai" + config_dir = tmp_path / "pkg" / ".fabric" fake_crew = FakeCrew(FakeRawResult()) calls: list[tuple[Any, ...]] = [] - monkeypatch.setattr(fabric_runner, "discover_packages", lambda workspace_root: {"pkg": crewai_dir}) + monkeypatch.setattr(fabric_runner, "discover_packages", lambda workspace_root: {"pkg": config_dir}) def fake_get_fabric_agent_config(path: Path, fabric_agent_name: str) -> dict[str, Any]: calls.append(("config", path, fabric_agent_name)) @@ -57,11 +57,11 @@ def fake_load_fabric_agent_from_config(config: dict[str, Any]) -> FakeCrew: assert result == "raw output" assert fake_crew.inputs == {"topic": "tests"} - assert calls == [("config", crewai_dir, "builder"), ("load", {"name": "builder"})] + assert calls == [("config", config_dir, "builder"), ("load", {"name": "builder"})] def test_run_fabric_agent_reports_available_packages(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(fabric_runner, "discover_packages", lambda workspace_root: {"known": Path(".crewai")}) + monkeypatch.setattr(fabric_runner, "discover_packages", lambda workspace_root: {"known": Path(".fabric")}) with pytest.raises(ValueError, match=r"Package 'missing' not found. Available: \['known'\]"): fabric_runner.run_fabric_agent("missing", "builder") @@ -86,7 +86,7 @@ def test_run_fabric_agent_from_path_loads_direct_config(monkeypatch: pytest.Monk monkeypatch.setattr(fabric_runner, "get_fabric_agent_config", lambda path, fabric_agent_name: {"path": path, "name": fabric_agent_name}) monkeypatch.setattr(fabric_runner, "load_fabric_agent_from_config", lambda config: fake_crew) - result = fabric_runner.run_fabric_agent_from_path(tmp_path / ".crewai", "builder", inputs={"x": 1}) + result = fabric_runner.run_fabric_agent_from_path(tmp_path / ".fabric", "builder", inputs={"x": 1}) assert result == "string output" assert fake_crew.inputs == {"x": 1} diff --git a/packages/agentic-fabric/tests/test_discovery.py b/packages/agentic-fabric/tests/test_discovery.py index d653ee6..7d2cc82 100644 --- a/packages/agentic-fabric/tests/test_discovery.py +++ b/packages/agentic-fabric/tests/test_discovery.py @@ -11,14 +11,14 @@ class TestDiscovery: """Tests for package discovery functionality.""" - def test_discover_packages_finds_crewai_directories(self, temp_workspace: Path) -> None: - """Test that discover_packages finds packages with .crewai directories.""" + def test_discover_packages_finds_fabric_workspace_directories(self, temp_workspace: Path) -> None: + """Test that discover_packages finds packages with .fabric directories.""" from agentic_fabric.core.discovery import discover_packages packages = discover_packages(workspace_root=temp_workspace) assert "sample" in packages - assert packages["sample"].exists() + assert packages["sample"].name == ".fabric" def test_discover_packages_finds_fabric_directories(self, tmp_path: Path) -> None: """Test that discover_packages finds framework-agnostic .fabric directories.""" @@ -123,7 +123,7 @@ def test_list_fabric_agents_returns_entries_from_manifest(self, temp_workspace: with patch( "agentic_fabric.core.discovery.discover_packages", - return_value={"sample": temp_workspace / "packages" / "sample" / ".crewai"}, + return_value={"sample": temp_workspace / "packages" / "sample" / ".fabric"}, ): fabric_agents_by_package = list_fabric_agents() @@ -138,7 +138,7 @@ def test_list_fabric_agents_filters_by_package_name(self, temp_workspace: Path) with patch( "agentic_fabric.core.discovery.discover_packages", - return_value={"sample": temp_workspace / "packages" / "sample" / ".crewai"}, + return_value={"sample": temp_workspace / "packages" / "sample" / ".fabric"}, ): fabric_agents_by_package = list_fabric_agents(package_name="sample") @@ -151,7 +151,7 @@ def test_list_fabric_agents_returns_empty_for_nonexistent_package(self, temp_wor with patch( "agentic_fabric.core.discovery.discover_packages", - return_value={"sample": temp_workspace / "packages" / "sample" / ".crewai"}, + return_value={"sample": temp_workspace / "packages" / "sample" / ".fabric"}, ): fabric_agents_by_package = list_fabric_agents(package_name="nonexistent") @@ -161,8 +161,8 @@ def test_load_manifest_parses_yaml(self, temp_workspace: Path) -> None: """Test that load_manifest parses YAML correctly.""" from agentic_fabric.core.discovery import load_manifest - crewai_dir = temp_workspace / "packages" / "sample" / ".crewai" - manifest = load_manifest(crewai_dir) + fabric_dir = temp_workspace / "packages" / "sample" / ".fabric" + manifest = load_manifest(fabric_dir) assert manifest is not None assert manifest.get("name") == "sample" @@ -199,11 +199,52 @@ def test_get_framework_from_config_dir(self) -> None: assert get_framework_from_config_dir(Path("/some/path/.langgraph")) == "langgraph" assert get_framework_from_config_dir(Path("/some/path/.strands")) == "strands" - def test_get_fabric_agent_config_includes_required_framework(self, temp_workspace: Path) -> None: - """Test that get_fabric_agent_config includes required_framework field.""" + def test_get_fabric_agent_config_leaves_runtime_open_for_fabric(self, temp_workspace: Path) -> None: + """Framework-agnostic .fabric configs should not require one runtime.""" from agentic_fabric.core.discovery import get_fabric_agent_config - crewai_dir = temp_workspace / "packages" / "sample" / ".crewai" + fabric_dir = temp_workspace / "packages" / "sample" / ".fabric" + config = get_fabric_agent_config(fabric_dir, "test_fabric_agent") + + assert config["required_framework"] is None + + def test_get_fabric_agent_config_includes_required_framework_for_crewai(self, tmp_path: Path) -> None: + """Framework-specific .crewai configs should require CrewAI.""" + from agentic_fabric.core.discovery import get_fabric_agent_config + + crewai_dir = tmp_path / ".crewai" + config_dir = crewai_dir / "fabric_agents" / "test_fabric_agent" + config_dir.mkdir(parents=True) + (crewai_dir / "manifest.yaml").write_text( + """ +name: crewai-specific +fabric_agents: + test_fabric_agent: + description: CrewAI-specific test fabric agent + agents: fabric_agents/test_fabric_agent/agents.yaml + tasks: fabric_agents/test_fabric_agent/tasks.yaml +""", + encoding="utf-8", + ) + (config_dir / "agents.yaml").write_text( + """ +test_agent: + role: Test Agent + goal: Test goal + backstory: Test backstory +""", + encoding="utf-8", + ) + (config_dir / "tasks.yaml").write_text( + """ +test_task: + description: Test task + expected_output: Test output + agent: test_agent +""", + encoding="utf-8", + ) + config = get_fabric_agent_config(crewai_dir, "test_fabric_agent") assert config["required_framework"] == "crewai" diff --git a/packages/agentic-fabric/tests/test_discovery_nested.py b/packages/agentic-fabric/tests/test_discovery_nested.py index 75617cf..0c9090d 100644 --- a/packages/agentic-fabric/tests/test_discovery_nested.py +++ b/packages/agentic-fabric/tests/test_discovery_nested.py @@ -93,9 +93,9 @@ def test_framework_filter_crewai(self, tmp_path: Path) -> None: def test_framework_filter_no_match(self, tmp_path: Path) -> None: """Filtering by framework with no matching dirs returns empty.""" pkg_dir = tmp_path / "packages" / "test_pkg" - fabric_dir = pkg_dir / ".crewai" - fabric_dir.mkdir(parents=True) - (fabric_dir / "manifest.yaml").write_text("name: test\nfabric_agents: {}") + crewai_dir = pkg_dir / ".crewai" + crewai_dir.mkdir(parents=True) + (crewai_dir / "manifest.yaml").write_text("name: test\nfabric_agents: {}") packages = discover_packages(workspace_root=tmp_path, framework="strands") assert packages == {} diff --git a/packages/agentic-fabric/tests/test_fabric_mocker.py b/packages/agentic-fabric/tests/test_fabric_mocker.py index 202a55d..55d037b 100644 --- a/packages/agentic-fabric/tests/test_fabric_mocker.py +++ b/packages/agentic-fabric/tests/test_fabric_mocker.py @@ -7,11 +7,11 @@ from pathlib import Path from typing import TYPE_CHECKING -from tests._fabric_mocker import ALL_FRAMEWORK_MODULES, CREWAI_MODULES, LANGGRAPH_MODULES, STRANDS_MODULES +from pytest_agentic_fabric.mocking import ALL_FRAMEWORK_MODULES, CREWAI_MODULES, LANGGRAPH_MODULES, STRANDS_MODULES if TYPE_CHECKING: - from tests._fabric_mocker import FabricMocker + from pytest_agentic_fabric.mocking import FabricMocker class TestFabricMockerBasics: @@ -70,4 +70,4 @@ class TestLocalFixtures: """Test reusable local fixtures.""" def test_temp_workspace_fixture(self, temp_workspace: Path) -> None: - assert (temp_workspace / "packages" / "sample" / ".crewai" / "manifest.yaml").exists() + assert (temp_workspace / "packages" / "sample" / ".fabric" / "manifest.yaml").exists() diff --git a/packages/agentic-fabric/tests/test_import_surfaces.py b/packages/agentic-fabric/tests/test_import_surfaces.py index fce6a8e..2188db8 100644 --- a/packages/agentic-fabric/tests/test_import_surfaces.py +++ b/packages/agentic-fabric/tests/test_import_surfaces.py @@ -14,6 +14,7 @@ PACKAGE_ROOT = Path(__file__).resolve().parents[1] WORKSPACE_ROOT = PACKAGE_ROOT.parents[1] +REPO_SPECIFIC_LEGACY_TOKEN = "otter" + "fall" def test_package_version_falls_back_when_not_installed(monkeypatch: pytest.MonkeyPatch) -> None: @@ -72,7 +73,7 @@ def test_runtime_source_does_not_reown_vendor_or_legacy_surfaces() -> None: disallowed = { "secrets_sync": "SecretSync Python access must route through vendor-fabric capabilities", "secretssync": "SecretSync direct-import spelling is not part of this layer", - "otterfall": "otterfall is repo-specific and not part of the agnostic package", + REPO_SPECIFIC_LEGACY_TOKEN: "repo-specific package naming is not part of the agnostic package", "agentic-crew": "fabric agent naming replaced old crew package naming", "connector_builder_crew": "connector builder code should use fabric naming", "ConnectorBuilderCrew": "connector builder code should use fabric naming", diff --git a/packages/agentic-fabric/tests/test_loader.py b/packages/agentic-fabric/tests/test_loader.py index 4836f29..47f2627 100644 --- a/packages/agentic-fabric/tests/test_loader.py +++ b/packages/agentic-fabric/tests/test_loader.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: - from tests._fabric_mocker import FabricMocker + from pytest_agentic_fabric.mocking import FabricMocker pytest.importorskip("crewai", reason="crewai not installed") diff --git a/packages/agentic-fabric/tests/test_manager.py b/packages/agentic-fabric/tests/test_manager.py index 28ae0ff..7c31f79 100644 --- a/packages/agentic-fabric/tests/test_manager.py +++ b/packages/agentic-fabric/tests/test_manager.py @@ -56,7 +56,7 @@ def test_delegate_with_string_input(self): package_name="test_pkg", ) - mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_packages = {"test_pkg": Path("/test/.fabric")} mock_config = { "name": "game_design", "agents": {}, @@ -87,7 +87,7 @@ def test_delegate_with_dict_input(self): package_name="test_pkg", ) - mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_packages = {"test_pkg": Path("/test/.fabric")} mock_config = {"name": "game_design", "agents": {}, "tasks": {}} with ( @@ -132,8 +132,8 @@ def test_delegate_auto_discovers_fabric_agent(self): manager = ManagerAgent(fabric_agents={"design": "game_design"}) mock_packages = { - "pkg1": Path("/pkg1/.crewai"), - "pkg2": Path("/pkg2/.crewai"), + "pkg1": Path("/pkg1/.fabric"), + "pkg2": Path("/pkg2/.fabric"), } mock_config = {"name": "game_design", "agents": {}, "tasks": {}} @@ -146,7 +146,7 @@ def test_delegate_auto_discovers_fabric_agent(self): # First package doesn't have the fabric agent. def get_config_side_effect(pkg_dir, fabric_agent_name): - if pkg_dir == Path("/pkg1/.crewai"): + if pkg_dir == Path("/pkg1/.fabric"): raise ValueError("Fabric agent not found") return mock_config @@ -165,21 +165,21 @@ def test_delegate_uses_cached_fabric_agent_config(self): mock_config = {"name": "game_design", "agents": {}, "tasks": {}} with ( - patch("agentic_fabric.core.manager.discover_packages", return_value={"test_pkg": Path("/test/.crewai")}), + patch("agentic_fabric.core.manager.discover_packages", return_value={"test_pkg": Path("/test/.fabric")}), patch("agentic_fabric.core.manager.get_fabric_agent_config", return_value=mock_config) as mock_get_config, patch("agentic_fabric.core.manager.run_fabric_agent_auto", side_effect=["first", "second"]) as mock_run, ): assert manager.delegate("design", "first task", framework="crewai") == "first" assert manager.delegate("design", {"task": "second task"}, framework="langgraph") == "second" - mock_get_config.assert_called_once_with(Path("/test/.crewai"), "game_design") + mock_get_config.assert_called_once_with(Path("/test/.fabric"), "game_design") assert mock_run.call_args_list[1].kwargs == {"inputs": {"task": "second task"}, "framework": "langgraph"} def test_delegate_fabric_agent_not_found_raises_error(self): """Test that fabric agent not found in any package raises ValueError.""" manager = ManagerAgent(fabric_agents={"design": "missing_fabric_agent"}) - mock_packages = {"pkg1": Path("/pkg1/.crewai")} + mock_packages = {"pkg1": Path("/pkg1/.fabric")} with ( patch("agentic_fabric.core.manager.discover_packages") as mock_discover, @@ -199,7 +199,7 @@ async def test_delegate_async(self): package_name="test_pkg", ) - mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_packages = {"test_pkg": Path("/test/.fabric")} mock_config = {"name": "game_design", "agents": {}, "tasks": {}} with ( @@ -223,7 +223,7 @@ async def test_delegate_parallel(self): package_name="test_pkg", ) - mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_packages = {"test_pkg": Path("/test/.fabric")} with ( patch("agentic_fabric.core.manager.discover_packages") as mock_discover, @@ -268,7 +268,7 @@ def test_delegate_sequential(self): package_name="test_pkg", ) - mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_packages = {"test_pkg": Path("/test/.fabric")} mock_config = {"name": "test", "agents": {}, "tasks": {}} with ( @@ -346,7 +346,7 @@ async def execute_workflow(self, task: str, **kwargs): package_name="test_pkg", ) - mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_packages = {"test_pkg": Path("/test/.fabric")} mock_config = {"name": "test", "agents": {}, "tasks": {}} with ( @@ -390,7 +390,7 @@ async def execute_workflow(self, task: str, **kwargs): package_name="test_pkg", ) - mock_packages = {"test_pkg": Path("/test/.crewai")} + mock_packages = {"test_pkg": Path("/test/.fabric")} mock_config = {"name": "test", "agents": {}, "tasks": {}} with ( diff --git a/packages/agentic-fabric/tests/test_runners.py b/packages/agentic-fabric/tests/test_runners.py index 17aad4c..e5ee30f 100644 --- a/packages/agentic-fabric/tests/test_runners.py +++ b/packages/agentic-fabric/tests/test_runners.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: - from tests._fabric_mocker import FabricMocker + from pytest_agentic_fabric.mocking import FabricMocker def reject_imports(*blocked_names: str): diff --git a/packages/pytest-agentic-fabric/README.md b/packages/pytest-agentic-fabric/README.md index 11c6e65..c4173b5 100644 --- a/packages/pytest-agentic-fabric/README.md +++ b/packages/pytest-agentic-fabric/README.md @@ -8,7 +8,17 @@ Fixtures: - `agentic_runtime_registry`: isolated mutable registry for tests. - `agentic_runtime_modules`: known runtime import module names. - `agentic_mock_runtime`: installs fake runtime modules into `sys.modules`. +- `agentic_fabric_mocker` / `fabric_mocker`: `FabricMocker` helper for + mocking CrewAI, LangGraph, Strands, and agentic-fabric internals. +- `mock_agentic_frameworks`: installs fake modules for all optional runtimes. +- `mock_crewai`, `mock_langgraph`, `mock_strands`: installs fake modules for + one optional runtime path. - `agentic_fabric_agent_config`: minimal framework-neutral fabric agent config. +- `simple_agent_config`, `simple_task_config`: reusable agent and task config + mappings. +- `simple_fabric_agent_config`, `multi_agent_fabric_agent_config`, + `fabric_agent_with_knowledge`: reusable fabric agent config mappings. +- `temp_fabric_dir`: temporary framework-agnostic `.fabric` directory. - `agentic_workspace`: temporary workspace with a discoverable `.fabric` package. `agentic_workspace` serializes the current `agentic_fabric_agent_config` @@ -20,3 +30,9 @@ the discovery layout consistent. Tests marked `@pytest.mark.agentic_e2e` or `@pytest.mark.agentic_runtime("crewai")` are skipped unless `--agentic-e2e` is passed. + +`FabricMocker` exposes `mock_crewai()`, `mock_langgraph()`, `mock_strands()`, +`mock_all_frameworks()`, framework-specific patch helpers, and +`patch_get_llm()`, `patch_discover_packages()`, +`patch_get_fabric_agent_config()`, and `patch_run_fabric_agent_auto()` for +tests that need to isolate fabric orchestration from optional runtime imports. diff --git a/packages/pytest-agentic-fabric/pyproject.toml b/packages/pytest-agentic-fabric/pyproject.toml index 1778ff7..7a0e197 100644 --- a/packages/pytest-agentic-fabric/pyproject.toml +++ b/packages/pytest-agentic-fabric/pyproject.toml @@ -23,6 +23,7 @@ classifiers = [ "Typing :: Typed", ] dependencies = [ + "pytest-mock>=3.15.1", "pytest>=9.1.1", "pyyaml>=6.0.3", ] @@ -30,6 +31,7 @@ dependencies = [ [project.optional-dependencies] tests = [ "pytest-asyncio>=1.4.0", + "pytest-mock>=3.15.1", "pytest>=9.1.1", ] typing = [ diff --git a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/__init__.py b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/__init__.py index 0806b1c..290e0d3 100644 --- a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/__init__.py +++ b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/__init__.py @@ -4,9 +4,24 @@ from importlib.metadata import PackageNotFoundError, version -from pytest_agentic_fabric.plugin import RUNTIME_MODULES +from pytest_agentic_fabric.mocking import ( + ALL_FRAMEWORK_MODULES, + CREWAI_MODULES, + LANGGRAPH_MODULES, + RUNTIME_MODULES, + STRANDS_MODULES, + FabricMocker, +) -__all__ = ["RUNTIME_MODULES", "__version__"] +__all__ = [ + "ALL_FRAMEWORK_MODULES", + "CREWAI_MODULES", + "LANGGRAPH_MODULES", + "RUNTIME_MODULES", + "STRANDS_MODULES", + "FabricMocker", + "__version__", +] try: __version__ = version("pytest-agentic-fabric") diff --git a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/mocking.py b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/mocking.py new file mode 100644 index 0000000..5551b43 --- /dev/null +++ b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/mocking.py @@ -0,0 +1,343 @@ +"""Mocking helpers for tests that exercise agentic-fabric integrations.""" + +from __future__ import annotations + +import sys + +from collections.abc import Generator, Iterable +from dataclasses import dataclass, field +from types import ModuleType +from typing import TYPE_CHECKING, Any + +import pytest + + +if TYPE_CHECKING: + from pytest_mock import MockerFixture + + +CREWAI_MODULES: tuple[str, ...] = ( + "crewai", + "crewai.knowledge", + "crewai.knowledge.source", + "crewai.knowledge.source.text_file_knowledge_source", +) +LANGGRAPH_MODULES: tuple[str, ...] = ("langgraph", "langgraph.prebuilt", "langchain_anthropic") +STRANDS_MODULES: tuple[str, ...] = ("strands",) +RUNTIME_MODULES: dict[str, tuple[str, ...]] = { + "crewai": CREWAI_MODULES, + "langgraph": LANGGRAPH_MODULES, + "strands": STRANDS_MODULES, +} +ALL_FRAMEWORK_MODULES: tuple[str, ...] = (*CREWAI_MODULES, *LANGGRAPH_MODULES, *STRANDS_MODULES) + + +@dataclass +class FabricMocker: + """Convenience wrapper around pytest-mock for optional agent runtime tests.""" + + mocker: MockerFixture + mocked_modules: dict[str, ModuleType] = field(default_factory=dict) + _original_modules: dict[str, ModuleType] = field(default_factory=dict) + _original_parent_attrs: dict[tuple[str, str], Any] = field(default_factory=dict) + _missing_parent_attrs: set[tuple[str, str]] = field(default_factory=set) + + @property + def MagicMock(self) -> Any: + """Return ``mocker.MagicMock`` for direct test customization.""" + return self.mocker.MagicMock + + @property + def Mock(self) -> Any: + """Return ``mocker.Mock`` for direct test customization.""" + return self.mocker.Mock + + @property + def patch(self) -> Any: + """Return ``mocker.patch`` for direct test customization.""" + return self.mocker.patch + + @property + def spy(self) -> Any: + """Return ``mocker.spy`` for direct test customization.""" + return self.mocker.spy + + @property + def stub(self) -> Any: + """Return ``mocker.stub`` for direct test customization.""" + return self.mocker.stub + + def mock_module(self, module_name: str) -> ModuleType: + """Install one fake import module and return it.""" + if module_name in self.mocked_modules: + return self.mocked_modules[module_name] + + module = ModuleType(module_name) + if any(name.startswith(f"{module_name}.") for name in ALL_FRAMEWORK_MODULES): + module.__path__ = [] # type: ignore[attr-defined] + + original = sys.modules.get(module_name) + if isinstance(original, ModuleType): + self._original_modules[module_name] = original + sys.modules[module_name] = module + self.mocked_modules[module_name] = module + self._attach_to_parent(module_name, module) + return module + + def mock_modules(self, module_names: Iterable[str]) -> dict[str, ModuleType]: + """Install multiple fake import modules.""" + return {name: self.mock_module(name) for name in module_names} + + def restore_modules(self) -> None: + """Restore fake modules and parent module attributes.""" + for module_name in list(self.mocked_modules): + if module_name in self._original_modules: + sys.modules[module_name] = self._original_modules[module_name] + else: + sys.modules.pop(module_name, None) + + for parent_name, child_name in self._missing_parent_attrs: + parent = sys.modules.get(parent_name) + if parent is not None and hasattr(parent, child_name): + delattr(parent, child_name) + + for (parent_name, child_name), value in self._original_parent_attrs.items(): + parent = sys.modules.get(parent_name) + if parent is not None: + setattr(parent, child_name, value) + + self.mocked_modules.clear() + self._original_modules.clear() + self._original_parent_attrs.clear() + self._missing_parent_attrs.clear() + + def mock_crewai(self) -> dict[str, ModuleType]: + """Install fake CrewAI modules with common entry points.""" + modules = self.mock_modules(CREWAI_MODULES) + setattr(modules["crewai"], "Agent", self.mocker.MagicMock()) + setattr(modules["crewai"], "Crew", self.mocker.MagicMock()) + setattr(modules["crewai"], "Task", self.mocker.MagicMock()) + setattr(modules["crewai"], "Process", self.mocker.MagicMock()) + setattr( + modules["crewai.knowledge.source.text_file_knowledge_source"], + "TextFileKnowledgeSource", + self.mocker.MagicMock(), + ) + return modules + + def mock_crewai_agent(self, **kwargs: Any) -> Any: + """Create a standalone CrewAI agent mock.""" + return self._mock_with_attrs(**kwargs) + + def mock_crewai_task(self, **kwargs: Any) -> Any: + """Create a standalone CrewAI task mock.""" + return self._mock_with_attrs(**kwargs) + + def mock_crewai_crew(self, result: str = "Test result", **kwargs: Any) -> Any: + """Create a CrewAI crew mock whose ``kickoff`` returns a raw result.""" + mock_crew = self._mock_with_attrs(**kwargs) + mock_result = self.mocker.MagicMock() + mock_result.raw = result + mock_crew.kickoff.return_value = mock_result + return mock_crew + + def patch_crewai_agent(self) -> Any: + """Patch ``crewai.Agent`` and return the patch mock.""" + self._ensure_crewai() + return self.mocker.patch("crewai.Agent", create=True) + + def patch_crewai_task(self) -> Any: + """Patch ``crewai.Task`` and return the patch mock.""" + self._ensure_crewai() + return self.mocker.patch("crewai.Task", create=True) + + def patch_crewai_crew(self) -> Any: + """Patch ``crewai.Crew`` and return the patch mock.""" + self._ensure_crewai() + return self.mocker.patch("crewai.Crew", create=True) + + def patch_crewai_process(self) -> Any: + """Patch ``crewai.Process`` and return the patch mock.""" + self._ensure_crewai() + return self.mocker.patch("crewai.Process", create=True) + + def patch_knowledge_source(self) -> Any: + """Patch or return the CrewAI text-file knowledge source mock.""" + self._ensure_crewai() + module = self.mocked_modules["crewai.knowledge.source.text_file_knowledge_source"] + return getattr(module, "TextFileKnowledgeSource") + + def mock_langgraph(self) -> dict[str, ModuleType]: + """Install fake LangGraph modules with common entry points.""" + modules = self.mock_modules(LANGGRAPH_MODULES) + setattr(modules["langgraph.prebuilt"], "create_react_agent", self.mocker.MagicMock()) + setattr(modules["langchain_anthropic"], "ChatAnthropic", self.mocker.MagicMock()) + return modules + + def patch_create_react_agent(self) -> Any: + """Patch or return ``langgraph.prebuilt.create_react_agent``.""" + self._ensure_langgraph() + return getattr(self.mocked_modules["langgraph.prebuilt"], "create_react_agent") + + def patch_chat_anthropic(self) -> Any: + """Patch or return ``langchain_anthropic.ChatAnthropic``.""" + self._ensure_langgraph() + return getattr(self.mocked_modules["langchain_anthropic"], "ChatAnthropic") + + def mock_langgraph_graph(self, result: str = "Test response") -> Any: + """Create a LangGraph graph mock whose ``invoke`` returns one message.""" + mock_graph = self.mocker.MagicMock() + mock_message = self.mocker.MagicMock() + mock_message.content = result + mock_graph.invoke.return_value = {"messages": [mock_message]} + return mock_graph + + def mock_strands(self) -> dict[str, ModuleType]: + """Install fake Strands modules with common entry points.""" + modules = self.mock_modules(STRANDS_MODULES) + setattr(modules["strands"], "Agent", self.mocker.MagicMock()) + return modules + + def patch_strands_agent(self) -> Any: + """Patch or return ``strands.Agent``.""" + self._ensure_strands() + return getattr(self.mocked_modules["strands"], "Agent") + + def mock_strands_agent(self, result: str = "Test response") -> Any: + """Create a Strands agent mock whose call returns text.""" + mock_agent = self.mocker.MagicMock() + mock_agent.return_value = result + return mock_agent + + def mock_all_frameworks(self) -> dict[str, ModuleType]: + """Install all supported optional runtime modules.""" + return {**self.mock_crewai(), **self.mock_langgraph(), **self.mock_strands()} + + def patch_get_llm(self, return_value: Any = None) -> Any: + """Patch ``agentic_fabric.config.llm.get_llm``.""" + mock = self.mocker.patch("agentic_fabric.config.llm.get_llm") + mock.return_value = self.mocker.MagicMock() if return_value is None else return_value + return mock + + def patch_discover_packages(self, packages: dict[str, Any] | None = None) -> Any: + """Patch ``agentic_fabric.core.discovery.discover_packages``.""" + mock = self.mocker.patch("agentic_fabric.core.discovery.discover_packages") + mock.return_value = {} if packages is None else packages + return mock + + def patch_get_fabric_agent_config(self, config: dict[str, Any] | None = None) -> Any: + """Patch ``agentic_fabric.core.discovery.get_fabric_agent_config``.""" + mock = self.mocker.patch("agentic_fabric.core.discovery.get_fabric_agent_config") + mock.return_value = {"name": "test", "agents": {}, "tasks": {}} if config is None else config + return mock + + def patch_run_fabric_agent_auto(self, result: str = "Test result") -> Any: + """Patch ``agentic_fabric.core.decomposer.run_fabric_agent_auto``.""" + mock = self.mocker.patch("agentic_fabric.core.decomposer.run_fabric_agent_auto") + mock.return_value = result + return mock + + def _attach_to_parent(self, module_name: str, module: ModuleType) -> None: + """Attach a child module to an already mocked parent module.""" + parent_name, separator, child_name = module_name.rpartition(".") + if not separator or parent_name not in sys.modules: + return + + parent = sys.modules[parent_name] + key = (parent_name, child_name) + if key not in self._original_parent_attrs and key not in self._missing_parent_attrs: + if hasattr(parent, child_name): + self._original_parent_attrs[key] = getattr(parent, child_name) + else: + self._missing_parent_attrs.add(key) + setattr(parent, child_name, module) + + def _ensure_crewai(self) -> None: + """Ensure CrewAI modules are mocked.""" + if "crewai" not in self.mocked_modules: + self.mock_crewai() + + def _ensure_langgraph(self) -> None: + """Ensure LangGraph modules are mocked.""" + if "langgraph" not in self.mocked_modules: + self.mock_langgraph() + + def _ensure_strands(self) -> None: + """Ensure Strands modules are mocked.""" + if "strands" not in self.mocked_modules: + self.mock_strands() + + def _mock_with_attrs(self, **kwargs: Any) -> Any: + """Create a mock object with supplied attributes.""" + mock = self.mocker.MagicMock() + for key, value in kwargs.items(): + setattr(mock, key, value) + return mock + + +@pytest.fixture +def agentic_fabric_mocker(mocker: MockerFixture) -> Generator[FabricMocker, None, None]: + """Provide ``FabricMocker`` for agentic-fabric tests.""" + fabric_mocker = FabricMocker(mocker=mocker) + yield fabric_mocker + fabric_mocker.restore_modules() + + +@pytest.fixture +def fabric_mocker(mocker: MockerFixture) -> Generator[FabricMocker, None, None]: + """Provide the concise ``FabricMocker`` fixture name.""" + fabric_mocker = FabricMocker(mocker=mocker) + yield fabric_mocker + fabric_mocker.restore_modules() + + +@pytest.fixture +def mock_agentic_frameworks(mocker: MockerFixture) -> Generator[dict[str, ModuleType], None, None]: + """Mock every optional agent runtime framework.""" + fabric_mocker = FabricMocker(mocker=mocker) + modules = fabric_mocker.mock_all_frameworks() + yield modules + fabric_mocker.restore_modules() + + +@pytest.fixture +def mock_crewai(mocker: MockerFixture) -> Generator[dict[str, ModuleType], None, None]: + """Mock CrewAI modules only.""" + fabric_mocker = FabricMocker(mocker=mocker) + modules = fabric_mocker.mock_crewai() + yield modules + fabric_mocker.restore_modules() + + +@pytest.fixture +def mock_langgraph(mocker: MockerFixture) -> Generator[dict[str, ModuleType], None, None]: + """Mock LangGraph modules only.""" + fabric_mocker = FabricMocker(mocker=mocker) + modules = fabric_mocker.mock_langgraph() + yield modules + fabric_mocker.restore_modules() + + +@pytest.fixture +def mock_strands(mocker: MockerFixture) -> Generator[dict[str, ModuleType], None, None]: + """Mock Strands modules only.""" + fabric_mocker = FabricMocker(mocker=mocker) + modules = fabric_mocker.mock_strands() + yield modules + fabric_mocker.restore_modules() + + +__all__ = [ + "ALL_FRAMEWORK_MODULES", + "CREWAI_MODULES", + "LANGGRAPH_MODULES", + "RUNTIME_MODULES", + "STRANDS_MODULES", + "FabricMocker", + "agentic_fabric_mocker", + "fabric_mocker", + "mock_agentic_frameworks", + "mock_crewai", + "mock_langgraph", + "mock_strands", +] diff --git a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py index 0770374..1714196 100644 --- a/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py +++ b/packages/pytest-agentic-fabric/src/pytest_agentic_fabric/plugin.py @@ -2,6 +2,8 @@ from __future__ import annotations +import os + from collections.abc import Callable from importlib.util import find_spec from pathlib import Path @@ -11,17 +13,15 @@ import pytest import yaml - -RUNTIME_MODULES: dict[str, tuple[str, ...]] = { - "crewai": ( - "crewai", - "crewai.knowledge", - "crewai.knowledge.source", - "crewai.knowledge.source.text_file_knowledge_source", - ), - "langgraph": ("langgraph", "langgraph.prebuilt", "langchain_anthropic"), - "strands": ("strands",), -} +from pytest_agentic_fabric.mocking import RUNTIME_MODULES +from pytest_agentic_fabric.mocking import ( # noqa: F401 + agentic_fabric_mocker, + fabric_mocker, + mock_agentic_frameworks, + mock_crewai, + mock_langgraph, + mock_strands, +) def pytest_addoption(parser: pytest.Parser) -> None: @@ -132,6 +132,140 @@ def agentic_fabric_agent_config() -> dict[str, Any]: } +@pytest.fixture +def check_api_key() -> None: + """Skip a live test unless ``ANTHROPIC_API_KEY`` is configured.""" + if not os.environ.get("ANTHROPIC_API_KEY"): + pytest.skip("ANTHROPIC_API_KEY not set") + + +@pytest.fixture +def check_aws_credentials() -> None: + """Skip a live test unless AWS credentials or a profile are configured.""" + has_key = os.environ.get("AWS_ACCESS_KEY_ID") + has_profile = os.environ.get("AWS_PROFILE") + if not (has_key or has_profile): + pytest.skip("AWS credentials not configured; set AWS_ACCESS_KEY_ID or AWS_PROFILE") + + +@pytest.fixture +def temp_fabric_dir(tmp_path: Path) -> Path: + """Create a temporary framework-agnostic ``.fabric`` directory.""" + fabric_dir = tmp_path / "test_package" / ".fabric" + (fabric_dir / "fabric_agents").mkdir(parents=True) + return fabric_dir + + +@pytest.fixture +def simple_agent_config() -> dict[str, Any]: + """Return a minimal individual agent configuration.""" + return { + "role": "Test Agent", + "goal": "Answer simple questions accurately", + "backstory": "You are a helpful assistant focused on providing clear, concise answers.", + } + + +@pytest.fixture +def simple_task_config() -> dict[str, Any]: + """Return a minimal task configuration.""" + return { + "description": "Answer the question: What is 2 + 2?", + "expected_output": "The answer to the mathematical question", + } + + +@pytest.fixture +def simple_fabric_agent_config( + simple_agent_config: dict[str, Any], + simple_task_config: dict[str, Any], +) -> dict[str, Any]: + """Return a minimal framework-neutral fabric agent configuration.""" + return { + "name": "test_fabric_agent", + "description": "A simple test fabric agent", + "agents": {"test_agent": simple_agent_config}, + "tasks": { + "test_task": { + **simple_task_config, + "agent": "test_agent", + } + }, + "knowledge_paths": [], + } + + +@pytest.fixture +def multi_agent_fabric_agent_config() -> dict[str, Any]: + """Return a fabric agent configuration with multiple collaborating agents.""" + return { + "name": "multi_agent_fabric_agent", + "description": "A fabric agent with multiple collaborating agents", + "agents": { + "researcher": { + "role": "Researcher", + "goal": "Gather and analyze information", + "backstory": "You are an expert researcher.", + }, + "writer": { + "role": "Writer", + "goal": "Write clear summaries", + "backstory": "You are a skilled technical writer.", + }, + }, + "tasks": { + "research_task": { + "description": "Research the topic: Python programming", + "expected_output": "Key facts about Python", + "agent": "researcher", + }, + "writing_task": { + "description": "Write a brief summary based on the research", + "expected_output": "A concise summary", + "agent": "writer", + "context": ["research_task"], + }, + }, + "knowledge_paths": [], + } + + +@pytest.fixture +def fabric_agent_with_knowledge(tmp_path: Path) -> dict[str, Any]: + """Return a fabric agent configuration with a local knowledge source.""" + knowledge_dir = tmp_path / "knowledge" + knowledge_dir.mkdir() + (knowledge_dir / "test_info.md").write_text( + """# Test Knowledge + +This is test knowledge about the color blue. +Blue is a primary color. +It is often associated with calmness and stability. +""", + encoding="utf-8", + ) + + return { + "name": "knowledge_fabric_agent", + "description": "A fabric agent with knowledge sources", + "agents": { + "knowledgeable_agent": { + "role": "Knowledge Expert", + "goal": "Answer questions using provided knowledge", + "backstory": "You have access to specialized knowledge.", + }, + }, + "tasks": { + "knowledge_task": { + "description": "What color is mentioned in the knowledge base?", + "expected_output": "The color mentioned", + "agent": "knowledgeable_agent", + }, + }, + "knowledge_paths": [knowledge_dir], + } + + @pytest.fixture def agentic_workspace(tmp_path: Path, agentic_fabric_agent_config: dict[str, Any]) -> Path: """Create a minimal workspace with a ``.fabric`` package.""" diff --git a/packages/pytest-agentic-fabric/tests/test_plugin.py b/packages/pytest-agentic-fabric/tests/test_plugin.py index fe6c637..e76fc53 100644 --- a/packages/pytest-agentic-fabric/tests/test_plugin.py +++ b/packages/pytest-agentic-fabric/tests/test_plugin.py @@ -5,6 +5,7 @@ import importlib import importlib.metadata import runpy +import sys from collections.abc import Callable from pathlib import Path @@ -14,6 +15,8 @@ import pytest import yaml +from pytest_agentic_fabric.mocking import FabricMocker + pytest_plugins = ("pytester",) @@ -76,12 +79,185 @@ def test_agentic_mock_runtime_sets_runtime_entrypoints( assert hasattr(modules[runtime], expected_attr) +def test_fabric_mocker_properties(fabric_mocker: FabricMocker, mocker: Any) -> None: + """The published mocker should expose pytest-mock helpers.""" + target = ModuleType("spy_target") + target.func = lambda: "ok" + + assert fabric_mocker.MagicMock() is not None + assert fabric_mocker.Mock() is not None + patched: dict[str, bool] = {} + fabric_mocker.patch.dict(patched, {"patched": True}) + assert patched["patched"] is True + spy = fabric_mocker.spy(target, "func") + assert target.func() == "ok" + spy.assert_called_once_with() + assert fabric_mocker.stub(name="stub") is not None + assert fabric_mocker.mocker is mocker + + +def test_fabric_mocker_module_restore_handles_original_parent_attr( + fabric_mocker: FabricMocker, +) -> None: + """Mocked nested modules should restore previous parent attributes.""" + parent = ModuleType("parent_fixture") + original_child = object() + parent.child = original_child + sys.modules["parent_fixture"] = parent + + try: + child = fabric_mocker.mock_module("parent_fixture.child") + + assert parent.child is child + + fabric_mocker.restore_modules() + + assert parent.child is original_child + assert "parent_fixture.child" not in sys.modules + finally: + sys.modules.pop("parent_fixture", None) + + +def test_fabric_mocker_restore_removes_new_parent_attr(fabric_mocker: FabricMocker) -> None: + """Mocked nested modules should remove parent attributes they created.""" + parent = ModuleType("new_parent_fixture") + sys.modules["new_parent_fixture"] = parent + + try: + child = fabric_mocker.mock_module("new_parent_fixture.child") + + assert parent.child is child + + fabric_mocker.restore_modules() + + assert not hasattr(parent, "child") + finally: + sys.modules.pop("new_parent_fixture", None) + + +def test_fabric_mocker_returns_same_module_on_repeat(fabric_mocker: FabricMocker) -> None: + """Repeated module mocks should reuse the same fake module.""" + assert fabric_mocker.mock_module("repeat_fixture") is fabric_mocker.mock_module("repeat_fixture") + + +def test_fabric_mocker_restores_original_module(fabric_mocker: FabricMocker) -> None: + """Existing sys.modules entries should be restored.""" + original = ModuleType("existing_fixture") + sys.modules["existing_fixture"] = original + + try: + mocked = fabric_mocker.mock_module("existing_fixture") + + assert mocked is not original + + fabric_mocker.restore_modules() + + assert sys.modules["existing_fixture"] is original + finally: + sys.modules.pop("existing_fixture", None) + + +def test_fabric_mocker_crewai_helpers(fabric_mocker: FabricMocker) -> None: + """CrewAI helpers should mock modules and common objects.""" + modules = fabric_mocker.mock_crewai() + + patched_agent = fabric_mocker.patch_crewai_agent() + assert modules["crewai"].Agent is patched_agent + assert fabric_mocker.patch_crewai_task() is not None + assert fabric_mocker.patch_crewai_crew() is not None + assert fabric_mocker.patch_crewai_process() is not None + assert fabric_mocker.patch_knowledge_source() is modules[ + "crewai.knowledge.source.text_file_knowledge_source" + ].TextFileKnowledgeSource + assert fabric_mocker.mock_crewai_agent(role="Tester").role == "Tester" + assert fabric_mocker.mock_crewai_task(description="Task").description == "Task" + assert fabric_mocker.mock_crewai_crew(result="done").kickoff().raw == "done" + + +def test_fabric_mocker_langgraph_helpers(fabric_mocker: FabricMocker) -> None: + """LangGraph helpers should mock modules and common objects.""" + modules = fabric_mocker.mock_langgraph() + + assert fabric_mocker.patch_create_react_agent() is modules["langgraph.prebuilt"].create_react_agent + assert fabric_mocker.patch_chat_anthropic() is modules["langchain_anthropic"].ChatAnthropic + assert fabric_mocker.mock_langgraph_graph(result="done").invoke()["messages"][0].content == "done" + + +def test_fabric_mocker_strands_helpers(fabric_mocker: FabricMocker) -> None: + """Strands helpers should mock modules and common objects.""" + modules = fabric_mocker.mock_strands() + + assert fabric_mocker.patch_strands_agent() is modules["strands"].Agent + assert fabric_mocker.mock_strands_agent(result="done")() == "done" + + +def test_fabric_mocker_patch_helpers_install_missing_frameworks(agentic_fabric_mocker: FabricMocker) -> None: + """Patch helpers should lazily install framework modules when needed.""" + crewai_agent = agentic_fabric_mocker.patch_crewai_agent() + langgraph_agent = agentic_fabric_mocker.patch_create_react_agent() + strands_agent = agentic_fabric_mocker.patch_strands_agent() + + assert sys.modules["crewai"].Agent is crewai_agent + assert sys.modules["langgraph"].prebuilt.create_react_agent is langgraph_agent + assert sys.modules["strands"].Agent is strands_agent + + +def test_fabric_mocker_all_frameworks(mock_agentic_frameworks: dict[str, ModuleType]) -> None: + """The all-framework fixture should install each supported runtime module.""" + assert "crewai" in mock_agentic_frameworks + assert "langgraph.prebuilt" in mock_agentic_frameworks + assert "strands" in mock_agentic_frameworks + + +def test_framework_specific_mock_fixtures( + mock_crewai: dict[str, ModuleType], + mock_langgraph: dict[str, ModuleType], + mock_strands: dict[str, ModuleType], +) -> None: + """Framework-specific fixtures should install only their runtime modules.""" + assert "crewai" in mock_crewai + assert "langgraph.prebuilt" in mock_langgraph + assert "strands" in mock_strands + + +def test_agentic_fabric_mocker_internal_patch_helpers(agentic_fabric_mocker: FabricMocker) -> None: + """Agentic-fabric patch helpers should target fabric names.""" + llm = object() + + assert agentic_fabric_mocker.patch_get_llm(return_value=llm).return_value is llm + assert agentic_fabric_mocker.patch_discover_packages({"pkg": Path(".fabric")}).return_value == { + "pkg": Path(".fabric"), + } + assert agentic_fabric_mocker.patch_get_fabric_agent_config({"name": "custom"}).return_value == { + "name": "custom", + } + assert agentic_fabric_mocker.patch_run_fabric_agent_auto("done").return_value == "done" + + def test_agentic_fabric_agent_config_fixture(agentic_fabric_agent_config: dict[str, Any]) -> None: """Minimal fabric agent config should be usable by runtime tests.""" assert agentic_fabric_agent_config["agents"]["tester"]["role"] == "Tester" assert agentic_fabric_agent_config["tasks"]["verify"]["agent"] == "tester" +def test_shared_config_fixtures( + simple_agent_config: dict[str, Any], + simple_task_config: dict[str, Any], + simple_fabric_agent_config: dict[str, Any], + multi_agent_fabric_agent_config: dict[str, Any], + fabric_agent_with_knowledge: dict[str, Any], + temp_fabric_dir: Path, +) -> None: + """Shared config fixtures should be framework-neutral and discoverable.""" + assert simple_agent_config["role"] == "Test Agent" + assert simple_task_config["description"].startswith("Answer the question") + assert simple_fabric_agent_config["tasks"]["test_task"]["agent"] == "test_agent" + assert multi_agent_fabric_agent_config["tasks"]["writing_task"]["context"] == ["research_task"] + assert fabric_agent_with_knowledge["knowledge_paths"][0].joinpath("test_info.md").exists() + assert temp_fabric_dir.name == ".fabric" + assert temp_fabric_dir.joinpath("fabric_agents").is_dir() + + def test_agentic_workspace_fixture(agentic_workspace: Path) -> None: """Workspace fixture should create a discoverable .fabric package.""" manifest = agentic_workspace / "packages" / "sample" / ".fabric" / "manifest.yaml" @@ -149,6 +325,36 @@ def test_workspace_uses_override(agentic_workspace): result.assert_outcomes(passed=1) +def test_check_api_key_fixture_skips_without_key(pytester: pytest.Pytester) -> None: + """Credential fixtures should skip live tests when credentials are absent.""" + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function\n") + pytester.makepyfile( + """ + def test_requires_api_key(check_api_key): + assert True + """ + ) + + result = pytester.runpytest("-q") + + result.assert_outcomes(skipped=1) + + +def test_check_aws_credentials_fixture_skips_without_credentials(pytester: pytest.Pytester) -> None: + """AWS credential fixture should skip live tests when credentials are absent.""" + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function\n") + pytester.makepyfile( + """ + def test_requires_aws(check_aws_credentials): + assert True + """ + ) + + result = pytester.runpytest("-q") + + result.assert_outcomes(skipped=1) + + def test_agentic_e2e_marker_skips_by_default(pytester: pytest.Pytester) -> None: """Runtime-dependent tests should skip unless --agentic-e2e is passed.""" pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function\n") diff --git a/tox.ini b/tox.ini index 3074b45..2fd2286 100644 --- a/tox.ini +++ b/tox.ini @@ -30,6 +30,7 @@ description = Run agentic-fabric package tests runner = uv-venv-runner package = skip deps = -e packages/agentic-fabric[tests] + -e packages/pytest-agentic-fabric[tests] commands = pytest packages/agentic-fabric/tests {posargs} [testenv:py{311,312,313,314}] @@ -59,6 +60,7 @@ package = skip deps = -e packages/agentic-fabric -e packages/pytest-agentic-fabric[tests] + pytest-mock>=3.15.1 commands = pytest packages/pytest-agentic-fabric/tests {posargs} [testenv:audit] diff --git a/uv.lock b/uv.lock index 95e0731..59d4835 100644 --- a/uv.lock +++ b/uv.lock @@ -41,6 +41,7 @@ dev = [ { name = "coverage", extra = ["toml"] }, { name = "mypy" }, { name = "pytest" }, + { name = "pytest-agentic-fabric", extra = ["tests"] }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-mock" }, @@ -74,6 +75,7 @@ strands = [ tests = [ { name = "coverage", extra = ["toml"] }, { name = "pytest" }, + { name = "pytest-agentic-fabric", extra = ["tests"] }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-mock" }, @@ -98,6 +100,7 @@ requires-dist = [ { name = "mypy", marker = "extra == 'typing'", specifier = ">=2.1.0" }, { name = "myst-parser", marker = "extra == 'docs'", specifier = ">=5.1.0,<6.0.0" }, { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.1.1" }, + { name = "pytest-agentic-fabric", extras = ["tests"], marker = "extra == 'tests'", editable = "packages/pytest-agentic-fabric" }, { name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=1.4.0" }, { name = "pytest-cov", marker = "extra == 'tests'", specifier = ">=7.1.0" }, { name = "pytest-mock", marker = "extra == 'tests'", specifier = ">=3.15.1" }, @@ -1772,6 +1775,7 @@ version = "0.1.0" source = { editable = "packages/pytest-agentic-fabric" } dependencies = [ { name = "pytest" }, + { name = "pytest-mock" }, { name = "pyyaml" }, ] @@ -1780,11 +1784,13 @@ dev = [ { name = "mypy" }, { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "pytest-mock" }, { name = "ruff" }, ] tests = [ { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "pytest-mock" }, ] typing = [ { name = "mypy" }, @@ -1797,6 +1803,8 @@ requires-dist = [ { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.1.1" }, { name = "pytest-agentic-fabric", extras = ["tests", "typing"], marker = "extra == 'dev'", editable = "packages/pytest-agentic-fabric" }, { name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=1.4.0" }, + { name = "pytest-mock", specifier = ">=3.15.1" }, + { name = "pytest-mock", marker = "extra == 'tests'", specifier = ">=3.15.1" }, { name = "pyyaml", specifier = ">=6.0.3" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.20" }, ] From 5a0736d48f4c865cf705813028f380b7b98a560a Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 05:10:39 -0500 Subject: [PATCH 32/35] docs: include pytest package api reference --- docs/api/index.rst | 4 ++-- docs/conf.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/api/index.rst b/docs/api/index.rst index 708ed11..db162fd 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -1,8 +1,8 @@ API Reference ============= -This section is generated from ``src/agentic_fabric`` with -``sphinx-autodoc2``. +This section is generated from the published workspace import surfaces, +``agentic_fabric`` and ``pytest_agentic_fabric``, with ``sphinx-autodoc2``. .. toctree:: :maxdepth: 2 diff --git a/docs/conf.py b/docs/conf.py index 96e0bc9..18f25e6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,7 +27,11 @@ { "path": "../packages/agentic-fabric/src/agentic_fabric", "module": "agentic_fabric", - } + }, + { + "path": "../packages/pytest-agentic-fabric/src/pytest_agentic_fabric", + "module": "pytest_agentic_fabric", + }, ] autodoc2_output_dir = "apidocs" autodoc2_render_plugin = "rst" From c749e4dd2ad7e84daa7f160939f179dd8eec8014 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 05:16:47 -0500 Subject: [PATCH 33/35] docs: add mcp adapter example --- packages/agentic-fabric/examples/README.md | 4 ++ .../agentic-fabric/examples/mcp_adapters.py | 45 +++++++++++++++++++ .../agentic-fabric/tests/test_examples.py | 28 ++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 packages/agentic-fabric/examples/mcp_adapters.py diff --git a/packages/agentic-fabric/examples/README.md b/packages/agentic-fabric/examples/README.md index b2f0002..75d9408 100644 --- a/packages/agentic-fabric/examples/README.md +++ b/packages/agentic-fabric/examples/README.md @@ -8,7 +8,11 @@ integration checks for the public API. python examples/discovery_workflow.py python examples/tool_registry.py python examples/runtime_context.py +python examples/mcp_adapters.py ``` The bundled `sample_workspace/` directory demonstrates a framework-agnostic `.fabric/` layout. + +`mcp_adapters.py` shows the agentic-fabric-owned MCP entry points and a +client configuration shape without importing the MCP SDK or provider packages. diff --git a/packages/agentic-fabric/examples/mcp_adapters.py b/packages/agentic-fabric/examples/mcp_adapters.py new file mode 100644 index 0000000..8a428e8 --- /dev/null +++ b/packages/agentic-fabric/examples/mcp_adapters.py @@ -0,0 +1,45 @@ +"""Inspect MCP adapter entry points without importing optional providers.""" + +from __future__ import annotations + +import json + +from typing import Any + +from agentic_fabric.tools import meshy_mcp, vendor_mcp + + +def inspect_mcp_adapters() -> dict[str, Any]: + """Return deterministic MCP adapter configuration examples.""" + return { + "extra": "mcp", + "entry_points": { + "vendor": "agentic-fabric-vendor-mcp", + "meshy": "agentic-fabric-meshy-mcp", + }, + "install_guidance": { + "mcp": vendor_mcp.MCP_INSTALL_MESSAGE, + "vendor": vendor_mcp.VENDOR_INSTALL_MESSAGE, + "meshy": meshy_mcp.VENDOR_INSTALL_MESSAGE, + }, + "client_config": { + "mcpServers": { + "vendor-fabric": { + "command": "agentic-fabric-vendor-mcp", + }, + "meshy": { + "command": "agentic-fabric-meshy-mcp", + "env": {"MESHY_API_KEY": ""}, + }, + } + }, + } + + +def main() -> None: + """Print MCP adapter metadata as JSON.""" + print(json.dumps(inspect_mcp_adapters(), indent=2, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/packages/agentic-fabric/tests/test_examples.py b/packages/agentic-fabric/tests/test_examples.py index 19965a9..9aa5647 100644 --- a/packages/agentic-fabric/tests/test_examples.py +++ b/packages/agentic-fabric/tests/test_examples.py @@ -9,6 +9,7 @@ from pathlib import Path from examples.discovery_workflow import DEFAULT_WORKSPACE, summarize_workspace +from examples.mcp_adapters import inspect_mcp_adapters from examples.runtime_context import inspect_runtime_context from examples.tool_registry import inspect_registry @@ -43,6 +44,21 @@ def test_runtime_context_example_inspects_agentic_data() -> None: assert summary["runtime_names"] == ["crewai", "langgraph", "strands"] +def test_mcp_adapters_example_inspects_owned_entry_points() -> None: + summary = inspect_mcp_adapters() + + assert summary["extra"] == "mcp" + assert summary["entry_points"] == { + "vendor": "agentic-fabric-vendor-mcp", + "meshy": "agentic-fabric-meshy-mcp", + } + assert "agentic-fabric[mcp]" in summary["install_guidance"]["mcp"] + assert "vendor-fabric" in summary["install_guidance"]["vendor"] + assert "vendor-fabric[meshy]" in summary["install_guidance"]["meshy"] + assert summary["client_config"]["mcpServers"]["vendor-fabric"]["command"] == "agentic-fabric-vendor-mcp" + assert summary["client_config"]["mcpServers"]["meshy"]["command"] == "agentic-fabric-meshy-mcp" + + def test_discovery_workflow_script_outputs_json() -> None: result = subprocess.run( [sys.executable, str(ROOT / "examples" / "discovery_workflow.py")], @@ -77,3 +93,15 @@ def test_runtime_context_script_outputs_json() -> None: parsed = json.loads(result.stdout) assert parsed["active_runtime"] == "crewai" + + +def test_mcp_adapters_script_outputs_json() -> None: + result = subprocess.run( + [sys.executable, str(ROOT / "examples" / "mcp_adapters.py")], + check=True, + capture_output=True, + text=True, + ) + + parsed = json.loads(result.stdout) + assert parsed["entry_points"]["vendor"] == "agentic-fabric-vendor-mcp" From 4daf7ea1c2e56c03df10e1f50741589857c49c79 Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 05:22:21 -0500 Subject: [PATCH 34/35] ci: add shipped examples gate --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 2 +- AGENTS.md | 1 + docs/development.rst | 2 ++ packages/agentic-fabric/README.md | 1 + .../agentic-fabric/tests/test_workflow_contracts.py | 6 +++--- tox.ini | 12 ++++++++++++ 7 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index e7afae6..c75ca2e 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -59,7 +59,7 @@ jobs: enable-cache: true - run: uv sync --all-packages --all-extras --dev - run: uv tool install tox --with tox-uv --with tox-gh - - run: tox -e lint,typecheck,audit,coverage,plugin,docs,build + - run: tox -e lint,typecheck,audit,examples,coverage,plugin,docs,build publish-package: name: Publish package to PyPI diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a38183..4d32611 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,4 +51,4 @@ jobs: enable-cache: true - run: uv sync --all-packages --all-extras --dev - run: uv tool install tox --with tox-uv --with tox-gh - - run: tox -e lint,typecheck,audit,coverage,plugin,docs,build + - run: tox -e lint,typecheck,audit,examples,coverage,plugin,docs,build diff --git a/AGENTS.md b/AGENTS.md index 967cb8e..b136cc7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -84,6 +84,7 @@ tox -e audit tox -e py311,py312,py313,py314 tox -e coverage tox -e plugin +tox -e examples tox -e docs tox -e build ``` diff --git a/docs/development.rst b/docs/development.rst index 0ab392b..3d3a697 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -13,6 +13,8 @@ Use tox for local validation: tox -e py313 tox -e py314 tox -e coverage + tox -e plugin + tox -e examples tox -e docs tox -e build diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index 380930f..0d1d62c 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -215,6 +215,7 @@ tox -e py313 tox -e py314 tox -e coverage tox -e plugin +tox -e examples tox -e build ``` diff --git a/packages/agentic-fabric/tests/test_workflow_contracts.py b/packages/agentic-fabric/tests/test_workflow_contracts.py index 33fc626..07d669d 100644 --- a/packages/agentic-fabric/tests/test_workflow_contracts.py +++ b/packages/agentic-fabric/tests/test_workflow_contracts.py @@ -40,11 +40,11 @@ def test_automerge_uses_base_context_without_checkout_or_secrets() -> None: ] -def test_ci_and_cd_quality_run_security_audit() -> None: - """CI and CD quality gates should include dependency auditing.""" +def test_ci_and_cd_quality_run_security_audit_and_examples() -> None: + """CI and CD quality gates should include auditing and shipped examples.""" for workflow_name in ("ci.yml", "cd.yml"): workflow = load_workflow(workflow_name) quality_steps = workflow["jobs"]["quality"]["steps"] tox_commands = [step["run"] for step in quality_steps if step.get("run", "").startswith("tox -e ")] - assert tox_commands == ["tox -e lint,typecheck,audit,coverage,plugin,docs,build"] + assert tox_commands == ["tox -e lint,typecheck,audit,examples,coverage,plugin,docs,build"] diff --git a/tox.ini b/tox.ini index 2fd2286..73f7ad4 100644 --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,7 @@ env_list = py314 coverage plugin + examples docs build min_version = 4.56.1 @@ -63,6 +64,17 @@ deps = pytest-mock>=3.15.1 commands = pytest packages/pytest-agentic-fabric/tests {posargs} +[testenv:examples] +description = Run shipped examples as standalone scripts +runner = uv-venv-runner +package = skip +deps = -e packages/agentic-fabric +commands = + python packages/agentic-fabric/examples/discovery_workflow.py + python packages/agentic-fabric/examples/tool_registry.py + python packages/agentic-fabric/examples/runtime_context.py + python packages/agentic-fabric/examples/mcp_adapters.py + [testenv:audit] description = Audit resolved optional, test, and typing dependencies runner = uv-venv-runner From 7daa64a842234cc90ef85a821706b697a40514ac Mon Sep 17 00:00:00 2001 From: Jon Bogaty Date: Sat, 27 Jun 2026 06:30:16 -0500 Subject: [PATCH 35/35] docs: make agentic repo guidance durable --- AGENTS.md | 175 +++++------------------------- README.md | 4 +- packages/agentic-fabric/README.md | 2 +- 3 files changed, 29 insertions(+), 152 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b136cc7..3954d6c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,77 +2,27 @@ This repository contains the `agentic-fabric` Python workspace. -## Active Goal - -Finish this repository after `extended-data` and `vendor-fabric`. Its immediate -purpose is to hold all agent runtime architecture and make the remaining -migration unambiguous. - -The active work branch is: - -```bash -codex/agentic-fabric-bootstrap -``` - -Use the canonical checkout only: - -```bash -/Users/jbogaty/src/jbcom/agentic-fabric -``` - -Do not create or continue side worktrees for this repo. - -## Related Repositories - -`agentic-fabric` depends on both upstream layers. Periodically check their local -and remote state before finalizing this repo. - -| Layer | Local checkout | Remote | Branch | -| --- | --- | --- | --- | -| Upstream base data | `/Users/jbogaty/src/jbcom/extended-data` | `https://github.com/jbcom/extended-data` | `codex/extended-data-polymorphic-container` | -| Upstream vendor layer | `/Users/jbogaty/src/jbcom/vendor-fabric` | `https://github.com/jbcom/vendor-fabric` | `codex/vendor-fabric-extended-data-boundary` | -| Current repo | `/Users/jbogaty/src/jbcom/agentic-fabric` | `https://github.com/jbcom/agentic-fabric` | `codex/agentic-fabric-bootstrap` | -| Legacy monorepo | `/Users/jbogaty/src/jbcom/extended-data-library` | `https://github.com/jbcom/extended-data-library` | `main` | -| Legacy agent repo | `/Users/jbogaty/src/jbcom/agentic` | `https://github.com/jbcom/agentic` | check locally | - ## Scope -`agentic-fabric` owns: - -- framework-agnostic fabric agent discovery and loading -- runner adapters for CrewAI, LangGraph, Strands, and local CLI tools -- agent-facing tool resolution and framework adapter utilities -- planned optional passthrough dependencies to `vendor-fabric` after - `vendor-fabric` is published -- `AgenticData` as the agent/runtime-aware extension of `VendorData` -- `pytest-agentic-fabric` for runner, tool, and framework fixture support -- Sphinx/Furo/autodoc2 docs for this workspace - -`extended-data` owns base data primitives, logging, inputs, files, redaction, -and generic workflows. `vendor-fabric` owns vendor connectors, provider-backed -sync, the SecretSync Python facade and `VendorData` capability surface, and -provider dispatch. `agentic-fabric` owns agent-facing wrappers over those -capabilities. +- `packages/agentic-fabric`: framework-agnostic agent discovery, runtime + selection, runner adapters, agent-facing tool wrappers, and `AgenticData` +- `packages/pytest-agentic-fabric`: pytest fixtures and mocks for agentic + runtime, tool, and framework tests +- `docs/`: Sphinx/Furo documentation and generated API reference -## Architecture Docs +## Layer Boundaries -Read these before changing code: +- `extended-data` owns base data containers, logging, input handling, files, + redaction, and generic workflows. +- `vendor-fabric` owns vendor connectors, provider-backed sync, SecretSync + Python facade/capability surfaces, provider capability metadata, and provider + dispatch. +- `agentic-fabric` owns runtime discovery, runner selection, framework + adapters, agent-facing tool wrappers, MCP surfaces, and orchestration. -- `docs/architecture.rst`: `AgenticData` and agent capability design -- `docs/pillars.rst`: package ownership principles -- `docs/vendor-fabric.rst`: dependency boundary with provider tooling - -Legacy parity sources for this repo are: - -- `/Users/jbogaty/src/jbcom/agentic` -- `/Users/jbogaty/src/jbcom/extended-data-library/packages/vendor-connectors/src/vendor_connectors/agentic` -- `/Users/jbogaty/src/jbcom/vendor-fabric/packages/vendor-fabric/src/vendor_fabric/agentic` -- `/Users/jbogaty/src/jbcom/vendor-fabric/packages/vendor-fabric/tests/agentic` -- `/Users/jbogaty/src/jbcom/vendor-fabric/packages/pytest-vendor-fabric/src/pytest_vendor_fabric/agentic` - -Move agent runtime code here. Leave provider connector code in `vendor-fabric`. -Do not add `vendor-fabric` passthrough extras until `vendor-fabric` exists on -PyPI. The package currently has no resolvable PyPI release under that name. +`AgenticData` extends `VendorData`; it should not bypass the vendor layer or +recreate provider dispatch. Keep SecretSync access routed through +`vendor-fabric` capabilities rather than importing the Go binding directly. ## Preferred Commands @@ -94,6 +44,16 @@ Use `uvx --with tox-uv tox -e ` when tox is not installed locally. Do not set `skip_missing_interpreters = true`. Python 3.11, 3.12, 3.13, and 3.14 are all part of the supported release contract. +## Expectations + +- Keep README files, Sphinx docs, examples, tests, and implementation aligned. +- Keep optional framework imports lazy and registry-backed. +- Keep provider-backed tools routed through `vendor-fabric`; do not call vendor + SDKs directly from agent runtime code. +- Use configured logging or Python logging/warnings/exceptions in library + runtime paths. CLI commands and examples may write user-facing output. +- Prefer durable package guidance over branch-specific migration instructions. + ## Release Flow Releases are managed by release-please: @@ -104,86 +64,3 @@ Releases are managed by release-please: Pages after releases are created. Do not hand-edit versions or tags outside release-please work. - -## Finish Criteria - -This repo is not done until: - -1. all agent runtime code from the legacy parity sources is represented here -2. provider connector code remains in `vendor-fabric` -3. `AgenticData` extends the vendor layer and routes agent/runtime capabilities -4. optional framework extras are lazy, discoverable, and documented -5. `pytest-agentic-fabric` is usable and separately publishable -6. docs, examples, tests, and README agree on the public API -7. Python 3.11 through 3.14 pass without skipped missing interpreters -8. a ready pull request is opened and reviewed -9. release-please and CD can publish packages and deploy docs - -## Active Finish Plan - -Use this section as the source of truth for finishing this WIP branch. - -### Current Branch - -```bash -git switch codex/agentic-fabric-bootstrap -``` - -Work only in `/Users/jbogaty/src/jbcom/agentic-fabric`. - -Finish `extended-data` first. Then finish `vendor-fabric`. This package depends -on the final `ExtendedData` and `VendorData` contracts. - -Before final validation, check local and remote state for both upstream repos. - -### Code - -- Keep runtime code under `packages/agentic-fabric`. -- Keep pytest helper code under `packages/pytest-agentic-fabric`. -- Implement `AgenticData` as a `VendorData` subclass with runtime context. -- Implement runner and tool capabilities through decorators collected by - `__init_subclass__`. -- Keep optional framework imports lazy and registry-backed. -- Route vendor-backed tools through `vendor-fabric`; do not call SDKs directly - from agent code. -- Add passthrough `vendor-fabric` extras only after `vendor-fabric` exists on - PyPI. As of this handoff, `vendor-fabric` has no resolvable PyPI release under - that name, so declaring passthrough extras makes `uv lock` fail. -- Use configured logging or Python logging/warnings/exceptions in library - runtime paths. CLI commands and examples may write user-facing output. - -### Docs - -- Keep README, Sphinx guides, examples, and API docs aligned. -- Keep public docs in reStructuredText under `docs/`; do not add authored - Markdown pages there. -- Keep architecture and pillar docs current when architecture changes. -- Document every supported runtime extra and unavailable-runtime behavior. -- Document framework priority and explicit runtime selection. - -### Tests - -- Cover runner registry behavior and capability dispatch. -- Cover optional framework dependencies as unavailable features with install - guidance. -- Cover `AgenticData` active-runtime routing and explicit-runtime routing. -- Keep `pytest-agentic-fabric` tested as its own package. -- Run Python 3.11 through 3.14. Do not skip missing interpreters. - -### GitHub - -- Create the public `jbcom/agentic-fabric` repo if it does not exist. -- Push the active branch. -- Open a ready pull request, not a draft. -- Handle CI failures. -- Inspect and resolve actionable review feedback. -- Squash merge only after the PR is approved and green. -- Confirm release-please and CD can publish packages and deploy docs. - -### Post-Finish AGENTS Cleanup - -After this finish plan is complete and the PR is merged, compress this file to -durable repository guidance only. Remove branch-specific WIP instructions, -legacy migration checklist items, and temporary hand-off details. Keep the -canonical package scope, normal validation commands, release flow notes, and -stable downstream/upstream boundaries. diff --git a/README.md b/README.md index e244d21..311c20f 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ This repository is the public `jbcom/agentic-fabric` workspace. Documentation: [jonbogaty.com/agentic-fabric](https://jonbogaty.com/agentic-fabric/) -Read `AGENTS.md` first in Codex sessions; it contains the active local -migration plan and hand-off instructions. +Read `AGENTS.md` first in Codex sessions; it contains durable repository +guidance, validation commands, and package boundary rules. The implemented surface includes `AgenticData`, lazy runtime registry metadata, capability decorators, vendor-tool references, and the sibling diff --git a/packages/agentic-fabric/README.md b/packages/agentic-fabric/README.md index 0d1d62c..e3f12d1 100644 --- a/packages/agentic-fabric/README.md +++ b/packages/agentic-fabric/README.md @@ -192,7 +192,7 @@ missing provider extras are visible. Full guides and API documentation are published at [jonbogaty.com/agentic-fabric](https://jonbogaty.com/agentic-fabric/). -`AGENTS.md` contains the active local migration plan for Codex sessions. +`AGENTS.md` contains durable repository guidance for Codex sessions. ## Documentation