From a968ea48ddc86af884ce80892ca5f8b18de4394a Mon Sep 17 00:00:00 2001 From: "Charles P. Cross" <8572939+cpdata@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:52:47 -0400 Subject: [PATCH 1/2] Add runtime gRPC server helpers and proto guard --- .github/workflows/ci.yml | 50 + AGENTS.md | 20 + CHANGELOG.md | 260 ++ CLEANUP.md | 86 + DISCREPANCIES.md | 53 + DUMMIES.md | 20 + Dockerfile | 24 + ENVIRONMENT_NEEDS.md | 34 + FINDINGS.md | 50 + ISSUES.md | 41 + Makefile | 45 +- NEEDED_FOR_TESTING.md | 77 + PLAN.md | 48 + PLANNING_THOUGHTS.md | 19 + PROJECT.md | 93 + README.md | 485 +-- README_OLD.md | 170 + RECOMMENDATIONS.md | 40 + RESUME_NOTES.md | 43 + ROADMAP.md | 27 + SETUP.md | 142 + SOT.md | 170 + TODO.md | 95 + docker-compose.yml | 72 +- docs/api.md | 102 + docs/architecture.md | 62 + docs/configuration.md | 54 + docs/development.md | 45 + docs/operations.md | 78 + docs/overview.md | 34 + docs/persistence.md | 62 + docs/pipelines.md | 47 + docs/retrieval.md | 59 + docs/telemetry.md | 33 + docs/testing.md | 72 + docs/troubleshooting.md | 32 + examples/extract_preprocess_store_example.py | 77 +- meshmind/api/grpc.py | 192 + meshmind/api/grpc_server.py | 98 + meshmind/api/memory_manager.py | 155 +- meshmind/api/rest.py | 123 + meshmind/api/service.py | 211 + meshmind/cli/__main__.py | 67 +- meshmind/cli/admin.py | 188 + meshmind/cli/ingest.py | 32 +- meshmind/client.py | 317 +- meshmind/core/bootstrap.py | 45 + meshmind/core/config.py | 84 +- meshmind/core/embeddings.py | 84 +- meshmind/core/observability.py | 80 + meshmind/core/similarity.py | 48 +- meshmind/core/types.py | 11 +- meshmind/core/utils.py | 62 +- meshmind/db/__init__.py | 14 + meshmind/db/base_driver.py | 60 +- meshmind/db/factory.py | 66 + meshmind/db/in_memory_driver.py | 188 + meshmind/db/memgraph_driver.py | 245 +- meshmind/db/neo4j_driver.py | 200 + meshmind/db/sqlite_driver.py | 308 ++ meshmind/llm_client.py | 259 ++ meshmind/models/registry.py | 23 +- meshmind/pipeline/compress.py | 52 +- meshmind/pipeline/consolidate.py | 168 +- meshmind/pipeline/expire.py | 8 +- meshmind/pipeline/extract.py | 47 +- meshmind/pipeline/preprocess.py | 114 +- meshmind/pipeline/store.py | 80 +- meshmind/protos/__init__.py | 12 + meshmind/protos/memory_service.proto | 83 + meshmind/protos/memory_service_pb2.py | 76 + meshmind/protos/memory_service_pb2_grpc.py | 226 ++ meshmind/retrieval/__init__.py | 39 + meshmind/retrieval/bm25.py | 74 +- meshmind/retrieval/fuzzy.py | 56 +- meshmind/retrieval/graph.py | 225 ++ meshmind/retrieval/rerank.py | 83 + meshmind/retrieval/search.py | 141 +- meshmind/retrieval/vector.py | 57 + meshmind/tasks/celery_app.py | 2 +- meshmind/tasks/scheduled.py | 177 +- meshmind/testing/__init__.py | 14 + meshmind/testing/fakes.py | 237 ++ meshmind/tests/conftest.py | 62 + meshmind/tests/docker/full-stack.yml | 49 + meshmind/tests/docker/memgraph.yml | 22 + meshmind/tests/docker/neo4j.yml | 28 + meshmind/tests/docker/redis.yml | 20 + meshmind/tests/test_api_examples.py | 70 + meshmind/tests/test_benchmark_scripts.py | 62 + meshmind/tests/test_cli_admin.py | 107 + meshmind/tests/test_client.py | 41 + meshmind/tests/test_counts_smoke.py | 52 + meshmind/tests/test_db_drivers.py | 194 + meshmind/tests/test_docs_guard.py | 25 + meshmind/tests/test_driver_factory.py | 30 + meshmind/tests/test_graph_retrieval.py | 121 + meshmind/tests/test_grpc_runtime.py | 80 + meshmind/tests/test_llm_client.py | 54 + meshmind/tests/test_memgraph_driver.py | 17 +- meshmind/tests/test_neo4j_driver.py | 40 + meshmind/tests/test_observability.py | 24 + meshmind/tests/test_pipeline_extract.py | 54 +- meshmind/tests/test_pipeline_maintenance.py | 85 +- .../tests/test_pipeline_preprocess_store.py | 167 +- meshmind/tests/test_protos_packaging.py | 13 + meshmind/tests/test_retrieval.py | 197 +- meshmind/tests/test_service_interfaces.py | 228 ++ meshmind/tests/test_setup_scripts.py | 31 + meshmind/tests/test_tasks_scheduled.py | 93 + pyproject.toml | 38 +- research/overview.md | 19 + run/install_setup.sh | 80 + run/maintenance_setup.sh | 83 + scripts/__init__.py | 0 scripts/benchmark_pagination.py | 90 + scripts/check_docs_sync.py | 102 + scripts/check_protos.py | 48 + scripts/consolidation_benchmark.py | 126 + scripts/evaluate_importance.py | 98 + scripts/generate_protos.py | 29 + uv.lock | 3448 ++++++++++++----- 122 files changed, 12305 insertions(+), 1874 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 AGENTS.md create mode 100644 CHANGELOG.md create mode 100644 CLEANUP.md create mode 100644 DISCREPANCIES.md create mode 100644 DUMMIES.md create mode 100644 Dockerfile create mode 100644 ENVIRONMENT_NEEDS.md create mode 100644 FINDINGS.md create mode 100644 ISSUES.md create mode 100644 NEEDED_FOR_TESTING.md create mode 100644 PLAN.md create mode 100644 PLANNING_THOUGHTS.md create mode 100644 PROJECT.md create mode 100644 README_OLD.md create mode 100644 RECOMMENDATIONS.md create mode 100644 RESUME_NOTES.md create mode 100644 ROADMAP.md create mode 100644 SETUP.md create mode 100644 SOT.md create mode 100644 TODO.md create mode 100644 docs/api.md create mode 100644 docs/architecture.md create mode 100644 docs/configuration.md create mode 100644 docs/development.md create mode 100644 docs/operations.md create mode 100644 docs/overview.md create mode 100644 docs/persistence.md create mode 100644 docs/pipelines.md create mode 100644 docs/retrieval.md create mode 100644 docs/telemetry.md create mode 100644 docs/testing.md create mode 100644 docs/troubleshooting.md create mode 100644 meshmind/api/grpc.py create mode 100644 meshmind/api/grpc_server.py create mode 100644 meshmind/api/rest.py create mode 100644 meshmind/api/service.py create mode 100644 meshmind/cli/admin.py create mode 100644 meshmind/core/bootstrap.py create mode 100644 meshmind/core/observability.py create mode 100644 meshmind/db/factory.py create mode 100644 meshmind/db/in_memory_driver.py create mode 100644 meshmind/db/neo4j_driver.py create mode 100644 meshmind/db/sqlite_driver.py create mode 100644 meshmind/llm_client.py create mode 100644 meshmind/protos/__init__.py create mode 100644 meshmind/protos/memory_service.proto create mode 100644 meshmind/protos/memory_service_pb2.py create mode 100644 meshmind/protos/memory_service_pb2_grpc.py create mode 100644 meshmind/retrieval/graph.py create mode 100644 meshmind/retrieval/rerank.py create mode 100644 meshmind/retrieval/vector.py create mode 100644 meshmind/testing/__init__.py create mode 100644 meshmind/testing/fakes.py create mode 100644 meshmind/tests/conftest.py create mode 100644 meshmind/tests/docker/full-stack.yml create mode 100644 meshmind/tests/docker/memgraph.yml create mode 100644 meshmind/tests/docker/neo4j.yml create mode 100644 meshmind/tests/docker/redis.yml create mode 100644 meshmind/tests/test_api_examples.py create mode 100644 meshmind/tests/test_benchmark_scripts.py create mode 100644 meshmind/tests/test_cli_admin.py create mode 100644 meshmind/tests/test_client.py create mode 100644 meshmind/tests/test_counts_smoke.py create mode 100644 meshmind/tests/test_db_drivers.py create mode 100644 meshmind/tests/test_docs_guard.py create mode 100644 meshmind/tests/test_driver_factory.py create mode 100644 meshmind/tests/test_graph_retrieval.py create mode 100644 meshmind/tests/test_grpc_runtime.py create mode 100644 meshmind/tests/test_llm_client.py create mode 100644 meshmind/tests/test_neo4j_driver.py create mode 100644 meshmind/tests/test_observability.py create mode 100644 meshmind/tests/test_protos_packaging.py create mode 100644 meshmind/tests/test_service_interfaces.py create mode 100644 meshmind/tests/test_setup_scripts.py create mode 100644 meshmind/tests/test_tasks_scheduled.py create mode 100644 research/overview.md create mode 100755 run/install_setup.sh create mode 100755 run/maintenance_setup.sh create mode 100644 scripts/__init__.py create mode 100644 scripts/benchmark_pagination.py create mode 100755 scripts/check_docs_sync.py create mode 100644 scripts/check_protos.py create mode 100644 scripts/consolidation_benchmark.py create mode 100644 scripts/evaluate_importance.py create mode 100644 scripts/generate_protos.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e5568b2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +name: CI + +on: + push: + branches: ["main", "review", "review-1"] + pull_request: + +env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install toolchain + run: | + pip install uv + uv pip install --system -e . + uv pip install --system ruff pyright typeguard toml-sort yamllint + - name: Lint and format checks + run: | + make fmt-check + make protos-check + - name: Docs guard + env: + BASE_REF: ${{ github.event.pull_request.base.sha || 'HEAD~1' }} + run: make docs-guard + + tests: + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install dependencies + run: | + pip install uv + uv pip install --system -e . + uv pip install --system pytest + - name: Run pytest + run: make test diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..09ebc4c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,20 @@ +# Agent Instructions + +## Documentation Workflow +- After each batch of changes, add a `CHANGELOG.md` entry with an ISO 8601 date/time stamp in United States Eastern time (include the timezone code, e.g., `America/New_York` or `ET`) and developer-facing detail (files, modules, functions, variables, and rationale). Every commit should correspond to a fresh entry. +- Maintain `README.md` as the canonical description of the project; update it whenever behaviour or workflows change. Archive older versions separately when requested. +- Keep the `docs/` wiki and provisioning guides (`SETUP.md`, `ENVIRONMENT_NEEDS.md`) in sync with code updates; add or revise the + relevant page whenever features, modules, or workflows change. +- After each iteration, refresh `ISSUES.md`, `SOT.md`, `PLAN.md`, `RECOMMENDATIONS.md`, `TODO.md`, and related documentation to stay in sync with the codebase. +- Ensure `TODO.md` retains the `Completed`, `Priority Tasks`, and `Recommended Waiting for Approval Tasks` sections, moving finished items under `Completed` at the end of every turn. +- Make every task in `TODO.md` atomic: each entry must describe a single, self-contained deliverable with enough detail to execute and verify without cross-referencing additional context. +- Update `RESUME_NOTES.md` at the end of every turn so the next session starts with accurate context. +- When beginning a turn, review `README.md`, `PROJECT.md`, `PLAN.md`, `RECOMMENDATIONS.md`, `ISSUES.md`, `SOT.md`, `ROADMAP.md`, `PLANNING_THOUGHTS.md`, and the `research/` wiki to harvest new actionable work. Maintain at least ten quantifiable, prioritised items in the `Priority Tasks` section of `TODO.md`, adding context or links when needed. +- Keep `ROADMAP.md`, `PLANNING_THOUGHTS.md`, and the `research/` docs aligned with each iteration when new discoveries or decisions occur. +- After completing any task, immediately update `TODO.md`, check for the next actionable item, and continue iterating until all unblocked `Priority Tasks` are exhausted for the session. +- Continuously loop through planning and execution: finish a task, document it, surface new follow-ups, and resume implementation so long as environment blockers allow. If extra guidance would improve throughput, extend these instructions proactively. + +## Style Guidelines +- Use descriptive Markdown headings starting at level 1 for top-level documents. +- Keep lines to 120 characters or fewer when practical. +- Prefer bullet lists for enumerations instead of inline commas. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9f3831e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,260 @@ +# Changelog + +## [2025-10-16T12:06:05-04:00 (America/New_York)] +### Added +- Introduced `meshmind/api/grpc_server.py` with `create_server`, `serve`, and `serve_forever` helpers so production deployments + can expose the canonical gRPC service via `grpc.aio` without rolling bespoke glue code. +- Added runtime coverage in `meshmind/tests/test_grpc_runtime.py` that ingests/searches over a live channel and asserts graceful + cancellation, plus `meshmind/tests/test_protos_packaging.py` to guarantee the proto ships with the distribution. +- Created `scripts/generate_protos.py` and `scripts/check_protos.py` alongside Makefile targets (`protos`, `protos-check`) and a + GitHub Actions step so protobuf drift is caught automatically; CI now installs the tooling with `uv --system` and runs the + verification command. + +### Changed +- Updated documentation (`README.md`, `SETUP.md`, `docs/api.md`, `docs/operations.md`, `docs/testing.md`, `docs/development.md`, + `PROJECT.md`, `PLAN.md`, `RECOMMENDATIONS.md`, `FINDINGS.md`, `ISSUES.md`, `SOT.md`, `ROADMAP.md`, `DUMMIES.md`, + `ENVIRONMENT_NEEDS.md`, `RESUME_NOTES.md`, `TODO.md`) to describe the new gRPC runtime workflow, follow-up tasks (CLI entry + point, docker-compose service), and proto maintenance process. +- Refreshed automation assets: `Makefile` now exposes `protos`/`protos-check`, `.github/workflows/ci.yml` invokes the new target + during linting, and `docs/testing.md` enumerates the additional runtime/packaging tests. + +## [2025-10-16T05:12:43-04:00 (America/New_York)] +### Added +- Authored `meshmind/protos/memory_service.proto` and generated `memory_service_pb2(_grpc).py`, exposing helpers in `meshmind.api.grpc` + so the gRPC surface now shares a canonical protobuf schema. +- Introduced `meshmind/protos/__init__.py` with a `data_path()` helper and configured `pyproject.toml` to ship `.proto` files with the + package. +- Added a `make benchmarks` target that executes `scripts/evaluate_importance.py`, `scripts/consolidation_benchmark.py`, and + `scripts/benchmark_pagination.py`, writing JSON snapshots to `build/benchmarks/` for quick regressions. + +### Changed +- Refactored `meshmind/api/grpc.py` to map protobuf messages to `MemoryPayload`/`TripletPayload`, ensuring search results preserve + timestamps and metadata while keeping convenience helpers for tests. +- Updated gRPC-related tests and docs (`meshmind/tests/test_service_interfaces.py`, `meshmind/tests/test_api_examples.py`, + `docs/api.md`, `README.md`, `docs/testing.md`, `docs/operations.md`, `PROJECT.md`, `PLAN.md`, `RECOMMENDATIONS.md`, `FINDINGS.md`, + `SOT.md`, `ROADMAP.md`, `ISSUES.md`, `RESUME_NOTES.md`, `DUMMIES.md`, `CLEANUP.md`, `ENVIRONMENT_NEEDS.md`, `NEEDED_FOR_TESTING.md`, + `TODO.md`) to reference the protobuf-backed interface and new benchmarking workflow. +- Declared `grpcio`, `grpcio-tools`, and `protobuf` as first-class dependencies (including the testing extra) and ensured setup scripts + continue validating optional packages. + +### Fixed +- Ensured generated protobuf files are packaged by default via `[tool.setuptools.package-data]`, preventing downstream installs from + missing the canonical schema. + +## [2025-10-16T04:52:41-04:00 (America/New_York)] +### Added +- Introduced benchmarking and evaluation tooling: `scripts/evaluate_importance.py` for heuristic summaries, `scripts/consolidation_benchmark.py` for consolidation throughput, and `scripts/benchmark_pagination.py` for driver pagination measurements, each covered by pytest (`meshmind/tests/test_benchmark_scripts.py`). +- Added REST/gRPC documentation smoke tests in `meshmind/tests/test_api_examples.py` to validate the new curl/grpcurl snippets against the FastAPI app and gRPC stub. + +### Changed +- Extended `meshmind/cli/admin.py` and `meshmind/tests/test_cli_admin.py` with `--max-attempts`, `--base-delay`, and `--run` overrides so maintenance retries can be tuned per invocation. +- Expanded consolidation coverage via `meshmind/tests/test_pipeline_preprocess_store.py` to exercise large synthetic datasets and ensure summaries/importance survive batching. +- Updated project documentation (`README.md`, `docs/api.md`, `docs/operations.md`, `SOT.md`, `PROJECT.md`, `PLAN.md`, `RECOMMENDATIONS.md`, `FINDINGS.md`, `ISSUES.md`, `ENVIRONMENT_NEEDS.md`, `NEEDED_FOR_TESTING.md`, `RESUME_NOTES.md`, `TODO.md`, `DUMMIES.md`, `CLEANUP.md`) to describe the CLI overrides, benchmarking utilities, shim retirement, and refreshed backlog priorities. + +### Removed +- Retired the Pydantic compatibility layer by deleting `meshmind/_compat/pydantic.py`, updating all imports to use first-party Pydantic models, and pruning `_compat` from the repository map. + +## [2025-10-14T22:51:20-04:00 (America/New_York)] +### Changed +- Documented LLM override precedence in `README.md`, expanded service documentation in `docs/api.md` and + `docs/configuration.md`, and clarified CLI guidance so per-request model/endpoint overrides are discoverable. +- Updated provisioning guidance in `SETUP.md` and `docs/operations.md` to describe the validation/dry-run flags in + `run/install_setup.sh` and `run/maintenance_setup.sh`, aligning with the new pytest smoke tests. +- Refreshed planning artifacts (`SOT.md`, `PLAN.md`, `PROJECT.md`, `RECOMMENDATIONS.md`, `FINDINGS.md`, `ISSUES.md`, `TODO.md`) + to capture timezone-aware timestamp defaults, FakeLLMClient coverage, and the completed documentation tasks. +- Synced testing references by elaborating on the FakeLLMClient behaviour in `docs/testing.md`, extending `DUMMIES.md`, and + updating environment guides (`ENVIRONMENT_NEEDS.md`, `NEEDED_FOR_TESTING.md`) to note optional packages now install with + network access. +- Revised `RESUME_NOTES.md` with the latest documentation work and remaining priorities to aid the next development session. + +## [2025-10-14T20:40:15-04:00 (America/New_York)] +### Added +- Introduced `meshmind/llm_client.py` with `LLMConfig`, a cached OpenAI-compatible `LLMClient`, and builders that hydrate + per-operation defaults from `LLM_*` environment variables so extraction, embeddings, and reranking share a single + provider abstraction. +- Added `meshmind/tests/test_llm_client.py` to verify configuration overrides and client caching behaviour without + requiring the real SDK. +- Authored `CLEANUP.md` to track post-restriction remediation work for compatibility shims now that full dependencies are + installable. + +### Changed +- Refactored `meshmind.client.MeshMind`, `meshmind/core/embeddings.py`, `meshmind/pipeline/extract.py`, and + `meshmind/retrieval/rerank.py` to consume the new `LLMClient`, cascade rerank defaults, and remove direct `openai` + imports. +- Expanded the CLI ingest command (`meshmind/cli/__main__.py`, `meshmind/cli/ingest.py`) with `--llm-*` flags that override + per-operation models/endpoints and wire them through to `MeshMind`. +- Updated configuration plumbing (`meshmind/core/config.py`, `scripts/check_docs_sync.py`) so `LLM_*` variables are first-class + settings and documentation guard checks map the new module to the wiki. +- Refreshed documentation (`README.md`, `docs/configuration.md`, `docs/pipelines.md`, `docs/retrieval.md`, `docs/operations.md`, + `docs/testing.md`, `docs/troubleshooting.md`, `SETUP.md`, `SOT.md`, `PROJECT.md`, `PLAN.md`, `RECOMMENDATIONS.md`, + `ISSUES.md`, `ENVIRONMENT_NEEDS.md`, `NEEDED_FOR_TESTING.md`, `FINDINGS.md`, `RESUME_NOTES.md`) to describe the provider-agnostic + LLM workflow, new CLI overrides, and related environment variables. +- Extended `TODO.md` and backlog artifacts to mark the LLM wrapper task complete, add follow-up work for surfacing overrides in + service payloads, and record the new cleanup plan. + +## [2025-10-14T19:44:39-04:00 (America/New_York)] +### Added +- Authored `DUMMIES.md` to catalogue compatibility shims (`meshmind/_compat/pydantic.py`), REST/gRPC stubs, Celery fallbacks, + and fake drivers with guidance on which artifacts to retire versus keep for offline testing. + +### Changed +- Updated `README.md`, `SOT.md`, `PLAN.md`, `RECOMMENDATIONS.md`, `PROJECT.md`, and `FINDINGS.md` to reference the new + compatibility inventory so contributors know where to track shim removal work. +## [2025-10-14T16:46:48-04:00 (America/New_York)] +### Changed +- Swapped the Memgraph dependency in `pyproject.toml` from `mgclient` to `pymgclient` and confirmed optional packages install + cleanly with the refreshed network access (`uv pip install`). +- Updated environment references—`ENVIRONMENT_NEEDS.md`, `NEEDED_FOR_TESTING.md`, `SETUP.md`, `README.md`, `README_OLD.md`, `SOT.md`, `PROJECT.md`, + `FINDINGS.md`, `ISSUES.md`, `TODO.md`, `docs/` wiki pages—to describe `pymgclient` as the Memgraph package while preserving + the runtime `mgclient` module references. +- Revised `AGENTS.md` to require Eastern Time timestamps with timezone codes for every changelog entry and aligned `RESUME_NOTES.md` + with the newly installed optional dependencies and confirmed internet availability. + +## [2025-10-14T15:53:42-04:00 (America/New_York)] +### Added +- Authored `run/install_setup.sh` and `run/maintenance_setup.sh` bash scripts that install system packages (`build-essential`, + `cmake`, `libssl-dev`, `libopenblas-dev`, etc.) and synchronize Python dependencies via `uv pip sync` so fresh and cached + environments can bootstrap optional tooling (`neo4j`, `mgclient`, `redis`, REST extras) once internet access is available. + +### Changed +- Updated `AGENTS.md` with an atomic-task requirement and refreshed `TODO.md` to prepend granular items for drafting + `CLEANUP.md`, introducing a provider-agnostic `meshmind/llm_client.py`, replacing direct OpenAI imports, and wiring cascaded + LLM overrides across configuration, CLI, API, tests, and documentation. +- Extended planning/backlog documents—`ISSUES.md`, `PLAN.md`, `RECOMMENDATIONS.md`, `SOT.md`, `RESUME_NOTES.md`—to capture the + upcoming LLM client refactor, dependency sync expectations, and the new automation scripts. +- Added setup guidance in `README.md` and `SETUP.md` pointing to the `run/` scripts so developers with sudo access can + bootstrap environments automatically. + +## [2025-10-17T18:45:00Z] +### Added +- Created a Dockerfile for integration workloads and introduced targeted Compose stacks + under `meshmind/tests/docker/` (Memgraph, Neo4j, Redis, full-stack) alongside a + developer-facing provisioning guide in `SETUP.md` to document service bootstrapping + commands and environment requirements. + +### Changed +- Expanded `pyproject.toml` to install optional dependencies (`fastapi`, + `uvicorn[standard]`, `neo4j`, `mgclient`, `redis`) by default and defined extras + (`dev`, `docs`, `testing`); updated the `Makefile` `install` target accordingly and + regenerated setup documentation across `README.md`, `docs/`, `PROJECT.md`, `PLAN.md`, + `SOT.md`, `NEEDED_FOR_TESTING.md`, `ENVIRONMENT_NEEDS.md`, `FINDINGS.md`, + `RECOMMENDATIONS.md`, and `RESUME_NOTES.md` to reference the new workflow and + credentials. +- Reworked the root `docker-compose.yml` to provision Memgraph, Neo4j, and Redis with + health checks and volumes, added Compose variants in `meshmind/tests/docker/`, and + refreshed onboarding materials (`SETUP.md`, `README.md`, `docs/configuration.md`, + `docs/operations.md`, `docs/testing.md`) to call out the new ports, credentials, and + teardown guidance. +- Replaced references to `pymgclient` with `mgclient` throughout dependency notes and + environment files to match the updated driver import. + +### Fixed +- Patched `meshmind/cli/admin.py` to import `argparse`, restoring CLI admin command + registration after the module refactor. +- Updated `.github/workflows/ci.yml` to pass `--system` to `uv pip install`, resolving + the "No virtual environment found" failure during lint/test setup. + +## [2025-10-16T18:30:00Z] +### Fixed +- Adjusted `meshmind/tests/test_service_interfaces.py::test_memory_service_ingest_and_search` to return a hydrated `Memory` + instance from the monkey-patched `list_memories` stub, ensuring pagination-aware search paths remain asserted while avoiding + empty result sets during verification. + +## [2025-10-16T12:00:00Z] +### Added +- Introduced pagination-aware graph access by adding `search_entities` and `count_entities` to every `GraphDriver` implementation, wiring a new `meshmind admin counts` CLI subcommand and REST `/memories/counts` route through `MemoryManager`, `MemoryService`, and the MeshMind client. +- Added `scripts/check_docs_sync.py` plus a Makefile target, CI step, and pytest coverage to guard documentation updates whenever code under mapped modules changes. + +### Changed +- Extended `MemoryManager.list_memories`, MeshMind client helpers, retrieval graph wrappers, and service adapters to forward `offset`, `limit`, and `query` hints, delegating filtering to the active driver before in-memory scoring. +- Updated examples and tests (`meshmind/tests/test_db_drivers.py`, `test_service_interfaces.py`, `test_graph_retrieval.py`, `test_cli_admin.py`, `test_client.py`, `test_docs_guard.py`) to cover pagination, counts, and driver-side search semantics. + +### Documentation +- Refreshed `README.md`, `PROJECT.md`, `PLAN.md`, `RECOMMENDATIONS.md`, `ISSUES.md`, `SOT.md`, `FINDINGS.md`, `AGENTS.md`, `TODO.md`, and the developer wiki (`docs/api.md`, `docs/development.md`, `docs/operations.md`, `docs/persistence.md`, `docs/retrieval.md`, `docs/troubleshooting.md`) to describe pagination, counts, docs-guard workflows, and updated service interfaces. +## [2025-10-15T15:30:00Z] +### Added +- Created a developer wiki under `docs/` covering architecture, pipelines, persistence, retrieval, configuration, testing, operations, telemetry, and development workflows so code changes stay synchronized with reference material. +- Authored `ENVIRONMENT_NEEDS.md` to request optional dependency installs and external services, plus `RESUME_NOTES.md` for session-to-session continuity. + +### Changed +- Expanded the `GraphDriver` contract to accept namespace and entity-label filters when listing entities, updating the in-memory, SQLite, Neo4j, and Memgraph drivers to push filtering into their native query layers. +- Propagated the new filtering through `MemoryManager`, `MeshMind.list_memories`, graph-backed retrieval wrappers, and service interfaces (REST/gRPC), ensuring hybrid searches hydrate only the required entity types. +- Updated tests (`meshmind/tests/test_graph_retrieval.py`, `test_pipeline_preprocess_store.py`, `test_service_interfaces.py`) to cover entity-label filtering across client, REST, and gRPC paths. + +### Documentation +- Refreshed `README.md`, `PROJECT.md`, `PLAN.md`, `RECOMMENDATIONS.md`, `ISSUES.md`, `SOT.md`, `DISCREPANCIES.md`, `FINDINGS.md`, `TODO.md`, and `AGENTS.md` to describe the new driver filtering, documentation workflow, environment checklist, and wiki requirements. + +## [2025-02-15T00:45:00Z] +### Added +- Introduced `meshmind/retrieval/graph.py` with hybrid/vector/regex/exact/BM25/fuzzy wrappers that hydrate candidates from the active `GraphDriver` before delegating to existing scorers, plus `meshmind/tests/test_graph_retrieval.py` to verify namespace filtering and hybrid integration. +- Added `meshmind/cli/admin.py` and wired `meshmind/cli/__main__.py` to expose `admin` subcommands for predicate management, maintenance telemetry, and graph connectivity checks; created `meshmind/tests/test_cli_admin.py` to cover the new flows. +- Created `meshmind/tests/test_neo4j_driver.py` and a `Neo4jGraphDriver.verify_connectivity` helper to exercise driver-level sanity checks without a live cluster. +- Logged importance score distributions via `meshmind/pipeline/preprocess.summarize_importance` so telemetry captures mean/stddev/recency metrics after scoring. + +### Changed +- Updated `MeshMind` search helpers (`meshmind/client.py`) to auto-load memories from the configured driver when `memories` is `None`, reusing the new graph-backed wrappers. +- Reworked `meshmind/pipeline/consolidate.py` to return a `ConsolidationPlan` with batch/backoff thresholds and skipped-group tracking; `meshmind/tasks/scheduled.consolidate_task` now emits skip counts and returns a structured summary. +- Tuned Python compatibility metadata to `>=3.11,<3.13` in `pyproject.toml` and refreshed docs (`README.md`, `NEEDED_FOR_TESTING.md`, `SOT.md`) accordingly. +- Enhanced `meshmind/pipeline/preprocess.py` to emit telemetry gauges for importance scoring and added `meshmind/tests/test_pipeline_preprocess_store.py::test_score_importance_records_metrics`. +- Expanded retrieval, CLI, and driver test coverage (`meshmind/tests/test_retrieval.py`, `meshmind/tests/test_tasks_scheduled.py`) to account for graph-backed defaults and new return types. + +### Documentation +- Updated `README.md`, `PROJECT.md`, `PLAN.md`, `SOT.md`, `FINDINGS.md`, `DISCREPANCIES.md`, `RECOMMENDATIONS.md`, `NEEDED_FOR_TESTING.md`, `ISSUES.md`, and `TODO.md` to describe graph-backed retrieval wrappers, CLI admin tooling, consolidation backoff behaviour, telemetry metrics, and revised Python support. +- Copied the refreshed README guidance into `README_OLD.md` as an archival reference while keeping `README.md` as the primary source. + +## [2025-10-14T14:57:47Z] +### Added +- Introduced `meshmind/_compat/pydantic.py` to emulate `BaseModel`, `Field`, and `ValidationError` when Pydantic is unavailable, enabling tests to run in constrained environments. +- Added `meshmind/testing/fakes.py` with `FakeMemgraphDriver`, `FakeRedisBroker`, and `FakeEmbeddingEncoder`, plus a package export and dedicated pytest coverage (`meshmind/tests/test_db_drivers.py`, `meshmind/tests/test_tasks_scheduled.py`). +- Created heuristics-focused test cases for consolidation outcomes, maintenance tasks, and the revised retrieval dispatcher to guarantee behaviour without external services. + +### Changed +- Replaced the constant importance assignment in `meshmind/pipeline/preprocess.score_importance` with a heuristic that factors token diversity, recency, metadata richness, and embedding magnitude. +- Rebuilt `meshmind/pipeline/consolidate` around a `ConsolidationOutcome` dataclass that merges metadata, averages embeddings, and surfaces removal IDs; `meshmind/tasks/scheduled.consolidate_task` now applies updates and deletes duplicates lazily via `_get_manager`/`_reset_manager` helpers. +- Hardened Celery maintenance tasks by logging driver initialization failures, tracking update counts, and returning deterministic totals; compression counts now reflect the number of persisted updates. +- Updated `meshmind/core/similarity`, `meshmind/retrieval/bm25`, and `meshmind/retrieval/fuzzy` with pure-Python fallbacks so numpy, scikit-learn, and rapidfuzz remain optional. +- Adjusted `meshmind/pipeline/extract.extract_memories` to defer `openai` imports until a default client is required, unblocking DummyLLM-driven tests. +- Reworked `meshmind/retrieval/search.search` to rerank the original (filtered) candidate ordering, prepend reranked results, and append hybrid-sorted fallbacks, preventing index drift when rerankers return relative positions. +- Normalised SQLite entity hydration in `meshmind/db/sqlite_driver._row_to_dict` so JSON metadata is decoded only when stored as strings. +- Refreshed pytest fixtures (`meshmind/tests/conftest.py`, `meshmind/tests/test_pipeline_preprocess_store.py`) to use deterministic encoders and driver doubles, ensuring CRUD and retrieval suites run without live services. + +### Documentation +- Promoted `README.md` as the single source of truth (archiving the previous copy in `README_OLD.md`) and documented the new heuristics, compatibility shims, and test doubles. +- Updated `NEEDED_FOR_TESTING.md` with notes about the compatibility layer, optional dependencies, and fake drivers. +- Reconciled `PROJECT.md`, `ISSUES.md`, `PLAN.md`, `SOT.md`, `RECOMMENDATIONS.md`, `DISCREPANCIES.md`, `FINDINGS.md`, `TODO.md`, and `CHANGELOG.md` to capture the new persistence behaviour, heuristics, fallbacks, and remaining roadmap items. + +## [Unreleased] - 2025-02-14 +### Added +- Configurable graph driver factory with in-memory, SQLite, Memgraph, and optional Neo4j implementations plus supporting tests. +- REST and gRPC service layers (with FastAPI stub fallback) for ingestion and retrieval, including coverage in the test suite. +- Observability utilities that collect metrics and structured logs across pipelines and scheduled Celery tasks. +- Docker Compose definition provisioning Memgraph, Redis, and a Celery worker for local development. +- Vector-only, regex, exact-match, and optional LLM rerank retrieval helpers with reranker utilities and exports. +- MeshMind client wrappers for hybrid, vector, regex, and exact searches plus driver accessors. +- Example script demonstrating triplet storage and diverse retrieval flows. +- Pytest fixtures for encoder and memory factories alongside new retrieval tests that avoid external services. +- Makefile targets for linting, formatting, type checks, and tests, plus a GitHub Actions workflow running lint and pytest. +- README_LATEST.md capturing the current implementation and CHANGELOG.md for release notes. + +### Changed +- Settings now surface `GRAPH_BACKEND`, Neo4j, and SQLite options while README/NEEDED_FOR_TESTING document the expanded setup. +- README, README_LATEST, and NEW_README were consolidated so the promoted README reflects current behaviour. +- PROJECT, PLAN, SOT, FINDINGS, DISCREPANCIES, ISSUES, RECOMMENDATIONS, and TODO were refreshed to capture new capabilities and + re-homed backlog items under a "Later" section. +- Updated `SearchConfig` to support rerank models and refreshed MeshMind documentation across PROJECT, PLAN, SOT, FINDINGS, + DISCREPANCIES, RECOMMENDATIONS, ISSUES, TODO, and NEEDED_FOR_TESTING files. +- Revised `meshmind.retrieval.search` to apply filters centrally, expose new search helpers, and integrate reranking. +- Exposed graph driver access on MeshMind and refreshed retrieval-facing examples and docs. + +### Fixed +- Example ingestion script now uses MeshMind APIs correctly and illustrates relationship persistence. +- Tests rely on fixtures rather than deprecated hooks, improving portability across environments without Memgraph/OpenAI. +## [2025-10-15T18:00:51-04:00 (America/New_York)] +### Added +- Authored strategic documentation (`ROADMAP.md`, `PLANNING_THOUGHTS.md`) and a competitor brief (`research/overview.md`) so planning artifacts capture milestone expectations, architectural questions, and market context alongside the existing SOT. +- Introduced REST/CLI smoke coverage for `/memories/counts` via `meshmind/tests/test_counts_smoke.py`, ensuring the new docs and README examples remain executable with the in-memory driver. + +### Changed +- Implemented configurable exponential backoff for consolidation/compression writes by extending `meshmind/core/config.py` with `MAINTENANCE_MAX_ATTEMPTS` / `MAINTENANCE_BASE_DELAY_SECONDS`, updating `meshmind/tasks/scheduled.py` to retry maintenance operations, and expanding `meshmind/tests/test_tasks_scheduled.py` to assert retry success/failure paths. +- Documented the new maintenance controls and telemetry across `README.md`, `docs/configuration.md`, `docs/operations.md`, `docs/telemetry.md`, `docs/testing.md`, `SOT.md`, `PLAN.md`, `PROJECT.md`, `RECOMMENDATIONS.md`, `FINDINGS.md`, `ISSUES.md`, `ENVIRONMENT_NEEDS.md`, and `NEEDED_FOR_TESTING.md`. +- Refreshed `TODO.md`, `RESUME_NOTES.md`, and related planning files to reflect the completed backlog items, highlight remaining priority tasks, and align with the new retry instrumentation. diff --git a/CLEANUP.md b/CLEANUP.md new file mode 100644 index 0000000..a8e6953 --- /dev/null +++ b/CLEANUP.md @@ -0,0 +1,86 @@ +# Cleanup Plan for Temporary Workarounds + +The following actions should be executed once the development environment consistently provides internet access and all +optional dependencies can be installed. Each item references the affected files, the temporary workaround currently in +place, and the exact remediation steps required to bring the implementation in line with production expectations. + +## Compatibility Layers + +### Replace Pydantic Shim (Completed) +- **Files**: `meshmind/_compat/pydantic.py`, modules importing from `meshmind._compat.pydantic`. +- **Status**: Completed – the shim has been removed, `pydantic>=2.11` is a required dependency, and all models/tests now consume the official APIs directly. + +### Retire FastAPI Stub +- **Files**: `meshmind/api/rest.py`, `meshmind/api/service.py`, `docs/api.md`, `SETUP.md`. +- **Current State**: A lightweight FastAPI replacement exposes REST endpoints without requiring the framework. +- **Action**: + 1. Install `fastapi`, `uvicorn`, and related extras via the setup scripts. + 2. Replace the stub implementation with a true FastAPI app using pydantic request/response models. + 3. Update tests to spin up the FastAPI test client instead of the stub. + 4. Refresh documentation to reflect the production stack only. + +### Replace gRPC Dataclass Shim (Completed) +- **Files**: `meshmind/api/grpc.py`, `meshmind/protos/memory_service.proto`, `meshmind/tests/test_service_interfaces.py`, `docs/api.md`. +- **Status**: Completed – protobuf definitions now live under `meshmind/protos`, generated modules back the Python stub, setup scripts install `grpcio`/`grpcio-tools`, and tests exercise the canonical schema. + +### Promote gRPC Server Implementation +- **Files**: `meshmind/api/grpc.py`, future server entry point, integration tests. +- **Current State**: Tests rely on the in-process stub; there is no deployable server or channel bootstrap yet. +- **Action**: + 1. Implement a gRPC server (synchronous or `grpc.aio`) that binds the generated service to an actual channel. + 2. Add integration tests (possibly using `grpc.aio.insecure_channel`) that cover ingestion, search, and memory counts against the running server. + 3. Document provisioning steps (`SETUP.md`, `docs/api.md`) and add CLI helpers or docker-compose services if required. + +## Task Scheduling Workarounds + +### Remove Celery Dummy App and Beat Fallback +- **Files**: `meshmind/tasks/celery_app.py`, `meshmind/tasks/scheduled.py`. +- **Current State**: Custom placeholders allow imports without Celery and Celery Beat. +- **Action**: + 1. Make `celery` a required dependency for maintenance tasks. + 2. Refactor scheduling utilities to import real Celery constructs and fail fast when misconfigured. + 3. Add integration tests that execute Celery workers against Redis or RabbitMQ in docker-compose. + 4. Delete fallback classes once coverage exists. + +## Testing Fakes + +### Evaluate Graph/Embedding/Test Fakes +- **Files**: `meshmind/testing/fakes.py`, `meshmind/tests/conftest.py`, `meshmind/tests/test_*`. +- **Current State**: In-memory drivers and dummy encoders enable offline unit tests. +- **Action**: + 1. Keep unit-test fakes for isolation but ensure parallel integration suites use live services. + 2. Document expectations in `docs/testing.md` and mark fakes clearly as test-only utilities. + 3. Remove any production code paths that accidentally import fakes. + +### Replace Memgraph Client Mock +- **Files**: `meshmind/tests/test_memgraph_driver.py`. +- **Current State**: Monkeypatches a fake `mgclient` module for driver tests. +- **Action**: + 1. Install `pymgclient` and run tests against a dockerized Memgraph instance. + 2. Retain the fake only for negative/offline coverage; guard it behind an explicit fixture flag. + +## Observability and Telemetry + +### Confirm Logging Metrics Alignment +- **Files**: `meshmind/core/observability.py`, `meshmind/pipeline/*`, `docs/telemetry.md`. +- **Current State**: Logging/metrics rely on standard library fallbacks. +- **Action**: + 1. Integrate preferred observability stack (e.g., OpenTelemetry) once dependencies are greenlit. + 2. Remove or demote placeholders that mimic external exporters. + +## Tooling and Automation + +### Finalize Setup Scripts +- **Files**: `run/install_setup.sh`, `run/maintenance_setup.sh`. +- **Current State**: Scripts install packages opportunistically. +- **Action**: + 1. Review installed package list after environment unblock and remove conditional guards. + 2. Ensure scripts configure CLI tools (e.g., `grpcurl`, `neo4j-admin`) that were previously skipped offline. + +### Lock Dependency Graph +- **Files**: `pyproject.toml`, `uv.lock`. +- **Current State**: Lockfile may exclude previously unavailable packages. +- **Action**: + 1. Regenerate `uv.lock` after all dependencies are installed. + 2. Commit the updated lockfile and note differences in `CHANGELOG.md`. + diff --git a/DISCREPANCIES.md b/DISCREPANCIES.md new file mode 100644 index 0000000..f992bff --- /dev/null +++ b/DISCREPANCIES.md @@ -0,0 +1,53 @@ +# README vs Implementation Discrepancies + +## Overview +- The legacy README has been superseded by `README.md`, which now reflects the implemented feature set. +- The current codebase delivers extraction, preprocessing, triplet persistence, CRUD helpers, and expanded retrieval strategies + that were missing when the README was written. +- Remaining gaps primarily involve pushing retrieval workloads into the graph backend, exporting observability to external sinks, and automated infrastructure provisioning. + +## API Surface +- ✅ `MeshMind` now exposes CRUD helpers (`create_memory`, `update_memory`, `delete_memory`, `list_memories`, triplet helpers) + that the README referenced implicitly. +- ✅ Triplet storage routes through `store_triplets` and `MemoryManager.add_triplet`, calling `GraphDriver.upsert_edge`. +- ⚠️ The README still references `register_entity`, `register_allowed_predicates`, and `add_predicate`; predicate management is + handled automatically but there is no public API matching those method names. +- ⚠️ README snippets showing `mesh_mind.store_memory(memory)` should be updated to call `store_memories([memory])` or the new + CRUD helpers. + +## Retrieval Capabilities +- ✅ Vector-only, regex, exact-match, hybrid, BM25, fuzzy, and optional LLM rerank searches exist in `meshmind.retrieval.search` + and are surfaced through `MeshMind` helpers. +- ⚠️ README implies retrieval queries the graph directly. Search helpers now fetch candidates from the configured driver when no + list is supplied but still score results in Python; Memgraph/Neo4j-native search remains future work. +- ⚠️ Named helpers like `search_facts` or `search_procedures` never existed; the README should reference the dispatcher plus + specialized helpers now available. + +## Data & Relationship Modeling +- ✅ Predicates are persisted automatically when storing triplets and tracked in `PredicateRegistry`. +- ⚠️ README examples that look up subjects/objects by name still do not match the implementation, which expects UUIDs. Add + documentation explaining how to resolve names to UUIDs before storing edges. +- ⚠️ Consolidation and expiry run via Celery jobs; README narratives should highlight that heuristics require further validation even though persistence is now wired up. + +## Configuration & Dependencies +- ✅ `README.md` and `ENVIRONMENT_NEEDS.md` document required environment variables, dependency guards, and setup steps. +- ⚠️ README still omits optional tooling now required by the Makefile/CI (ruff, pyright, typeguard, toml-sort, yamllint); + highlight these prerequisites more prominently. +- ✅ Python version support in `pyproject.toml` now pins `>=3.11,<3.13`, matching the dependency landscape documented in the README. + +## Example Code Paths +- ✅ Updated example scripts demonstrate extraction, triplet creation, and multiple retrieval strategies. +- ⚠️ Legacy README code that instantiates custom Pydantic entities remains inaccurate; extraction returns `Memory` objects and + validates `entity_label` names only. +- ⚠️ Search examples should be updated to show the new helper functions and optional rerank usage instead of nonexistent + `search_facts`/`search_procedures` calls. + +## Tooling & Operations +- ✅ Makefile and CI workflows now exist, aligning with README promises about automation once the README is refreshed. +- ✅ Docker Compose now provisions Memgraph, Redis, and a Celery worker; README sections should highlight the workflow and + caveats for environments lacking container tooling. +- ⚠️ Celery tasks still depend on optional infrastructure; README should clarify that heuristics and scheduling need production hardening even though persistence now works. + +## Documentation State +- Continue promoting `README.md` as the authoritative guide and propagate updates to supporting docs + (`SOT.md`, `PLAN.md`, `ENVIRONMENT_NEEDS.md`, `docs/`). diff --git a/DUMMIES.md b/DUMMIES.md new file mode 100644 index 0000000..1a5e257 --- /dev/null +++ b/DUMMIES.md @@ -0,0 +1,20 @@ +# Dummies and Compatibility Artifacts + +The following table tracks all temporary shims, fake implementations, and stub services that were introduced while the +sandbox lacked internet access or external infrastructure. Each row notes what the component does today and the +recommended next step now that full dependencies can be installed. + +| Component | Location | Purpose | Current Usage | Recommended Action | +| --- | --- | --- | --- | --- | +| FastAPI REST stub | `meshmind/api/rest.py` (`RestAPIStub`, `create_app`) | Exposes REST behaviour without the FastAPI dependency. | Tests use the stub when FastAPI is not installed; production should use FastAPI. | Keep temporarily for offline tests but plan to gate it behind an explicit test flag once FastAPI becomes mandatory. | +| gRPC stub implementation | `meshmind/api/grpc.py` (`GrpcServiceStub` + generated protobuf helpers) | Provides an in-process service implementation backed by the canonical proto schema so tests can exercise the service layer without spinning up gRPC infrastructure. | Unit tests and docs rely on the stub while production traffic should flow through the new asyncio helpers in `meshmind.api.grpc_server`. | Keep the stub for tests; package the new server behind a CLI entry point and retire any ad-hoc wrappers once infrastructure is provisioned. | +| Celery dummy app | `meshmind/tasks/celery_app.py` (`_DummyCeleryApp`) | Allows module imports when Celery is missing. | Unit tests rely on the dummy to avoid Celery; production should use real Celery. | Retire dummy once Celery is a hard dependency; until then ensure tests explicitly exercise the real app when installed. | +| Celery beat fallback | `meshmind/tasks/scheduled.py` (`crontab` shim) | Supplies a no-op crontab when Celery beat is missing. | Prevents import errors in test environments without Celery. | Remove once Celery is required; otherwise guard usage with feature flags. | +| Fake graph/storage drivers | `meshmind/testing/fakes.py` (`FakeMemgraphDriver`, `FakeRedisBroker`, `FakeEmbeddingEncoder`) | Provide offline stand-ins for Memgraph, Redis, and embedding models. | Pytest fixtures and documentation rely on these for isolation. | Keep as long as offline tests are desired; supplement with integration suites that use real services. | +| Fake LLM client | `meshmind/testing/fakes.py` (`FakeLLMClient`) | Records per-request overrides and emits deterministic responses so tests exercise reranking without installing the OpenAI SDK. | Service/interface tests (`meshmind/tests/test_service_interfaces.py`, `test_client.py`) and the CLI fixtures inject this stub when `openai` is unavailable. | Keep for unit tests; add integration tests with real providers once keys and network access are provisioned. | +| Dummy encoder fixture | `meshmind/tests/conftest.py` (`dummy_encoder`) and dependent tests | Supplies a lightweight embedding encoder for search tests. | Used across retrieval and service tests to avoid network calls. | Keep for unit tests; add integration coverage with real encoders once APIs are configured. | +| Fake mgclient module | `meshmind/tests/test_memgraph_driver.py` (monkeypatch of `mgclient`) | Simulates the Memgraph client so driver code runs without the native binary. | Enables driver unit tests without installing `mgclient`. | Replace with real `mgclient`-backed tests once package access is ensured; keep shim for fallback coverage. | + +## Retired Items + +- **Pydantic compatibility shim (`meshmind/_compat/pydantic.py`)** – Removed now that the project installs `pydantic>=2.11` during setup. All models import directly from Pydantic and tests confirm no fallback is required. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7fe6f4e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM python:3.11-slim + +ENV PIP_NO_CACHE_DIR=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + cmake \ + libssl-dev \ + libkrb5-dev \ + curl \ + git \ + && rm -rf /var/lib/apt/lists/* + +COPY . /app + +RUN pip install uv \ + && uv pip install --system -e .[dev,docs,testing] + +CMD ["bash"] diff --git a/ENVIRONMENT_NEEDS.md b/ENVIRONMENT_NEEDS.md new file mode 100644 index 0000000..f455524 --- /dev/null +++ b/ENVIRONMENT_NEEDS.md @@ -0,0 +1,34 @@ +# Tasks for Human Project Manager + +- Keep the Python package layer aligned with the project extras during base image + refreshes. The `run/install_setup.sh` and `run/maintenance_setup.sh` scripts now + install the full optional stack (neo4j driver, `pymgclient`, Redis, Celery extras, + FastAPI/Uvicorn, LLM tooling, and developer linters/testers). Ensure cached + environments either run the maintenance script or bake these dependencies into the + image so cold starts do not regress coverage. +- Provide system-level build dependencies for the graph drivers (e.g., `build-essential`, + `cmake`, `libssl-dev`, `libkrb5-dev`) so `pymgclient` (and its `mgclient` module) install cleanly. +- Provision external services and credentials (compose files now exist under the project + root and `meshmind/tests/docker/`): + - Neo4j instance reachable from the execution environment with `NEO4J_URI`, + `NEO4J_USERNAME`, `NEO4J_PASSWORD` (defaults to `neo4j` / `meshminD123`). + - Memgraph instance with `MEMGRAPH_URI`, `MEMGRAPH_USERNAME`, `MEMGRAPH_PASSWORD` + (anonymous access acceptable locally). + - Redis instance with `REDIS_URL`. + - LLM provider API key (`LLM_API_KEY` or fallback `OPENAI_API_KEY`) plus any + alternative base URLs/models required for OpenRouter, Azure, or Google-hosted + endpoints so the new `llm_client` overrides can be exercised end-to-end. + - Default maintenance retry configuration (`MAINTENANCE_MAX_ATTEMPTS`, `MAINTENANCE_BASE_DELAY_SECONDS`) tuned for the deployed graph backend; surface recommended values once integration tests run against live clusters. *(Future refinement request once infra is available.)* +- Supply datasets/fixtures (future request) representing large knowledge graphs to + stress-test consolidation heuristics and pagination under load. +- Maintain outbound package download access to PyPI and vendor repositories; this + session confirmed package installation works when the network is open, and future + sessions need the same capability to refresh locks or install new optional + integrations. +- Enable Docker or container runtime access (future request) so the provided + `docker-compose.yml` files can run inside this environment; alternatively, provision + remote services accessible to CI. +- Document credential management procedures and rotation cadence so secrets stay current. +- Keep gRPC tooling (`grpcio`, `grpcio-tools`, protobuf compiler) available in cached environments; the proto definitions now + back the production stubs and runtime server (`meshmind.api.grpc_server`). `scripts/generate_protos.py` regenerates bindings + and `scripts/check_protos.py` enforces drift in CI, so ensure the toolchain runs successfully during maintenance scripts. diff --git a/FINDINGS.md b/FINDINGS.md new file mode 100644 index 0000000..70e5c8c --- /dev/null +++ b/FINDINGS.md @@ -0,0 +1,50 @@ +# Findings + +## General Observations +- Core modules are now wired through the `MeshMind` client, including CRUD, triplet storage, and retrieval helpers. Graph-backed wrappers fetch namespace/entity-label filtered candidates from the configured driver automatically; remaining integration work focuses on server-side query optimisation and heuristic evaluation loops. +- Optional dependencies are largely guarded behind lazy imports or factory functions, improving portability. Environments still need to install tooling referenced by the Makefile and CI (ruff, pyright, typeguard, toml-sort, yamllint). `DUMMIES.md` now tracks remaining shims (FastAPI/gRPC/Celery) and documents retired items such as the former Pydantic fallback. +- LLM usage is now centralized in `meshmind.llm_client` with per-operation defaults cascading from `LLM_*` environment variables and CLI overrides, reducing the risk of divergent configurations across pipelines and retrieval. REST/gRPC payloads now provide matching override dictionaries so services can experiment per request. +- Timestamp helpers default to timezone-aware UTC, eliminating naive datetime outputs that previously leaked into maintenance pipelines and compatibility shims. +- The gRPC interface is now defined by `meshmind/protos/memory_service.proto`; generated Python modules back the `GrpcServiceStub` + so tests and future services share a canonical schema without bespoke dataclasses. Runtime helpers in + `meshmind.api.grpc_server` now expose the service over `grpc.aio`, and CI enforces proto drift via `scripts/check_protos.py`. +- Documentation artifacts (`README.md`, `SOT.md`, `ENVIRONMENT_NEEDS.md`, `docs/`) stay current when updated with each iteration; the legacy README has been archived as `README_OLD.md`. A docs-guard script now enforces synchronized updates during CI. + +## Dependency & Environment Notes +- `MeshMind` defers driver creation until persistence is required, enabling workflows without the `mgclient` module (install `pymgclient` to obtain it) and selecting between + in-memory, SQLite, Memgraph, or Neo4j backends. CLI helpers (`meshmind admin graph`) now expose connectivity sanity checks. +- Package management via `uv` now succeeds (optional dependencies installed via `uv pip install` this session); keep network access available so `uv.lock` regeneration can proceed when prioritized. Provisioning scripts (`run/install_setup.sh`, `run/maintenance_setup.sh`) now validate optional extras and support dry-run mode, with pytest coverage ensuring the flags continue to behave. +- Project metadata now advertises Python `>=3.11,<3.13`, aligning with available wheels for optional dependencies. +- Encoder registration occurs during bootstrap, but custom deployments must ensure compatible models are registered before + extraction or hybrid search. +- The OpenAI embedding adapter still expects dictionary-like responses; adapting to SDK objects remains on the backlog. +- Repository audit confirmed there are no lingering `import openai` statements outside guarded sections; the client and + embedding modules already encapsulate the SDK behind optional imports, easing the upcoming refactor to a dedicated + wrapper. +- Celery tasks initialize lazily, yet Redis/Memgraph services are still required at runtime. Docker Compose now provisions + Memgraph, Neo4j, and Redis, while targeted stacks under `meshmind/tests/docker/` support integration testing. `SETUP.md` + explains provisioning and teardown commands, and `scripts/benchmark_pagination.py` offers an offline way to measure driver throughput before integrating external services. + +## Data Flow & Persistence +- Triplet storage now persists relationships and tracks predicates automatically, closing an earlier data-loss gap. +- Consolidation and compression utilities now persist updates through the maintenance tasks, enforce configurable exponential backoff (`MAINTENANCE_MAX_ATTEMPTS`, `MAINTENANCE_BASE_DELAY_SECONDS`), and surface skipped groups; larger-scale validation remains necessary. +- Importance scoring uses heuristics (token diversity, recency, metadata richness, embedding magnitude) and now records telemetry summaries; continued evaluation will raise retrieval quality. + +## CLI & Tooling +- CLI ingestion bootstraps encoders and entities automatically and now ships `admin` subcommands for predicate maintenance, telemetry dumps, graph connectivity checks, namespace/entity counts, and per-run maintenance overrides (`--max-attempts`, `--base-delay`, `--run`). Automated smoke tests cover the counts workflow end-to-end using the in-memory driver. External backends still require valid credentials and running services. +- The Makefile introduces lint, format, type-check, test, and docs-guard targets, plus Docker helpers. External tooling installation is + required before targets succeed. +- A `make benchmarks` target now executes the synthetic benchmarking scripts and writes JSON summaries under `build/benchmarks/`, + simplifying regression tracking for importance scoring, consolidation retries, and pagination performance. +- GitHub Actions now run formatting checks, the docs guard, and pytest on push/PR, providing basic CI coverage. + +## Testing & Quality +- Pytest suites rely on fixtures (`memory_factory`, `dummy_encoder`) and fake drivers to run without external services. Coverage now includes graph-backed retrieval wrappers, Neo4j connectivity shims, CLI admin flows, counts smoke tests, docs guard checks, Celery consolidation telemetry/backoff handling, the new benchmarking scripts, REST/gRPC example payloads, the asyncio gRPC server runtime, and proto packaging guards. +- Type checking via `pyright` and runtime checks via `typeguard` are exposed in the Makefile; dependency installation is + necessary for full validation. + +## Documentation +- `README.md` documents setup, pipelines, retrieval, tooling, service interfaces, observability, and the new maintenance retry controls. +- Strategic documentation (`ROADMAP.md`, `PLANNING_THOUGHTS.md`, `research/overview.md`) summarises milestones, decision context, and competitor analysis for future planning. +- Supporting docs (`ISSUES.md`, `PLAN.md`, `RECOMMENDATIONS.md`, `SOT.md`) reflect the latest capabilities and highlight remaining + gaps, aiding onboarding and future planning. diff --git a/ISSUES.md b/ISSUES.md new file mode 100644 index 0000000..f728ec2 --- /dev/null +++ b/ISSUES.md @@ -0,0 +1,41 @@ +# Issues Checklist + +## Blockers +- [x] MeshMind client fails without the `mgclient` module; introduce lazy driver initialization or documented in-memory fallback. +- [x] Register a default embedding encoder (OpenAI or sentence-transformers) during startup so extraction and hybrid search can run. +- [x] Update OpenAI integration to match the current SDK (Responses API payload, embeddings API response structure). +- [x] Replace eager `tiktoken` imports in `meshmind.core.utils` and `meshmind.pipeline.compress` with guarded, optional imports. +- [x] Align declared Python requirement with supported dependencies (project now pins Python >=3.11,<3.13). + +- [ ] Maintain pip/uv package download access (confirmed working on 2025-10-15) so dependency lock regeneration can proceed reliably across sessions. +## High Priority +- [x] Provide configuration documentation and examples for Memgraph, Redis, and OpenAI environment variables. +- [x] Add automated tests or smoke checks that run without external services (mock OpenAI, stub Memgraph driver). +- [x] Create real docker-compose services for Memgraph and Redis or remove the placeholder file. +- [x] Centralize LLM provider usage behind a configurable client wrapper to remove direct `openai` imports scattered through the codebase. +- [x] Surface LLM override fields via REST/gRPC payloads and integration tests so service clients can select providers/models like the CLI. +- [ ] Document Neo4j driver requirements and verify connectivity against a live cluster (CLI connectivity checks exist but still need validation against a real instance). +- [ ] Exercise the new namespace/entity-label filtering against live Memgraph/Neo4j datasets to confirm Cypher predicates behave as expected. +- [ ] Regenerate `uv.lock` to reflect the updated dependency set (`pymgclient`, `fastapi`, `uvicorn`, extras) so CI tooling stays in sync. +## Medium Priority +- [x] Persist results from consolidation and compression tasks back to the database (currently in-memory only). +- [x] Refine `Memory.importance` scoring to reflect actual ranking heuristics instead of a constant. +- [x] Add vector, regex, and exact-match search helpers to match stated feature set or update documentation to demote them. +- [x] Harden Celery tasks to initialize dependencies lazily and log failures when the driver is unavailable. +- [x] Validate consolidation heuristics on larger datasets to measure ranking accuracy and resource usage (synthetic fixtures and benchmark scripts cover scale; rerun with production datasets when available). +- [x] Document and implement a conflict-resolution/backoff strategy for consolidation when merged metadata conflicts (configurable via `MAINTENANCE_MAX_ATTEMPTS` and `MAINTENANCE_BASE_DELAY_SECONDS`). +- [x] Revisit the compatibility shim once production environments support Pydantic 2.x so the real models can be restored (shim removed; native Pydantic models now required). +- [x] Replace the gRPC dataclass shim with generated protobuf definitions; follow-up integration tests remain pending until a real gRPC server is provisioned. +- [x] Implement a production-ready gRPC server (leveraging the generated protobuf modules). Async helpers now live in `meshmind.api.grpc_server`; integration tests remain blocked on staging infrastructure. +- [ ] Add end-to-end gRPC integration tests (deploy server + grpcurl smoke) once staging infrastructure is provisioned. +- [ ] Push graph-backed retrieval into Memgraph/Neo4j search capabilities once available (current wrappers now filter/paginate server-side but still score vectors in Python). +- [ ] Reconcile tests that depend on `Memory.pre_init` and outdated OpenAI interfaces with the current implementation. +- [x] Expose `memory_counts` via a gRPC endpoint to keep service interfaces aligned. +- [x] Add linting, formatting, and type-checking tooling to improve code quality. + +- [ ] Validate the new Docker Compose stacks (root and `meshmind/tests/docker/`) on an environment with container support and document host requirements (ports, resources). +## Low Priority / Nice to Have +- [x] Offer alternative storage backends (in-memory driver, SQLite, etc.) for easier local development. +- [x] Provide an administrative dashboard or CLI commands for listing namespaces, counts, and maintenance statistics (CLI admin subcommands now expose predicates, telemetry, and graph checks). +- [ ] Publish onboarding guides and troubleshooting FAQs for contributors. +- [ ] Explore plugin registration for embeddings and retrieval strategies to reduce manual wiring. diff --git a/Makefile b/Makefile index 6693190..65d00c3 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,48 @@ -.PHONY: install lint fmt test docker +.PHONY: install lint fmt fmt-check typecheck test check docker clean docs-guard benchmarks protos protos-check + +BENCH_DIR := build/benchmarks install: - pip install -e . + python -m pip install -e .[dev,docs,testing] lint: - ruff . + ruff check . fmt: - isort . - black . + ruff format . + +fmt-check: + ruff format --check . + ruff check . + toml-sort --check pyproject.toml + yamllint .github/workflows + +docs-guard: + python scripts/check_docs_sync.py --base $${BASE_REF:-origin/main} + +protos: + python scripts/generate_protos.py + +protos-check: + python scripts/check_protos.py + +typecheck: + pyright + python -m typeguard --check meshmind test: - pytest + pytest + +benchmarks: + mkdir -p $(BENCH_DIR) + python scripts/evaluate_importance.py --synthetic-count 48 --namespace benchmarks --output $(BENCH_DIR)/importance.json + python scripts/consolidation_benchmark.py --namespaces 2 --groups 6 --duplicates 3 --iterations 2 --output $(BENCH_DIR)/consolidation.json + python scripts/benchmark_pagination.py --backend memory --count 500 --page-size 100 --iterations 2 --output $(BENCH_DIR)/pagination.json + +check: fmt-check lint typecheck test docs-guard protos-check + +clean: + rm -rf .pytest_cache .ruff_cache docker: - docker-compose up \ No newline at end of file + docker compose up diff --git a/NEEDED_FOR_TESTING.md b/NEEDED_FOR_TESTING.md new file mode 100644 index 0000000..e42f9f1 --- /dev/null +++ b/NEEDED_FOR_TESTING.md @@ -0,0 +1,77 @@ +# Needed for Testing MeshMind + +> **Note:** `ENVIRONMENT_NEEDS.md` lists infrastructure and package requests for the human project manager. Use this document for developer-side setup details. + +## Python Runtime +- Python 3.11 or 3.12 is recommended; project metadata now pins `>=3.11,<3.13` because several dependencies (`pymgclient` (exposes the `mgclient` module), + `sentence-transformers`) do not yet publish wheels for 3.13. +- Use a virtual environment (`uv`, `venv`, or `conda`) to isolate dependencies. + +## Python Dependencies +- Install the project editable (with extras) using `pip install -e .[dev,docs,testing]` or + `uv pip install --system -e .[dev,docs,testing]` from the repository root. +- Core functionality relies on the OpenAI SDK (or compatible fork), `pydantic`, and `pydantic-settings`; the project now + requires Pydantic 2.x directly (the legacy shim has been removed). +- Optional packages improve specific workflows (now bundled in the editable install extras so they install automatically when + running the provisioning scripts): + - `numpy`, `scikit-learn`, and `rapidfuzz` accelerate similarity and lexical search (pure-Python fallbacks are bundled). + - `sentence-transformers`, `tiktoken`, and `pymgclient` enable local embeddings, compression, and Memgraph connectivity. + - `celery[redis]` activates scheduled maintenance with a Redis broker. + - `fastapi` + `uvicorn[standard]` power the REST adapter when exercising HTTP APIs. + - `grpcio` + `grpcio-tools` + `protobuf` enable the generated gRPC clients/servers that replace the former dataclass shim. +- Optional drivers: install `neo4j` if exercising the Neo4j backend; SQLite support ships with the standard library. The + provisioning scripts validate that the extras expose `neo4j`, `pymgclient`, `fastapi`, and `uvicorn`. +- Development tooling referenced by the Makefile and CI (installed via `.[dev,docs,testing]`): + - `ruff` for linting and formatting. + - `pyright` for static type checks. + - `typeguard` for runtime type enforcement (`python -m typeguard --check meshmind`). + - `toml-sort` and `yamllint` for configuration validation. + - `mkdocs`/`mkdocs-material` for future documentation publishing. +- Optional helpers for local workflows: `pytest-cov`, `pre-commit`, `httpx`/`grpcio-tools` (for service interface experimentation). + +## External Services and Infrastructure +- **Graph backend** options: + - In-memory / SQLite require no external services (set `GRAPH_BACKEND=memory` or `sqlite`). + - **Memgraph** reachable via `MEMGRAPH_URI` with credentials exported in `MEMGRAPH_USERNAME`/`MEMGRAPH_PASSWORD`. + - **Neo4j** reachable via `NEO4J_URI` with credentials `NEO4J_USERNAME`/`NEO4J_PASSWORD` when the optional driver is installed + (defaults supplied in `docker-compose.yml`). Use `meshmind admin graph --backend neo4j` to verify connectivity once + credentials are configured. +- **Redis** for Celery task queues, referenced through `REDIS_URL`. +- **LLM provider access** for extraction, embeddings, and reranking (`LLM_API_KEY` or fallback `OPENAI_API_KEY`, plus optional + `LLM_*_BASE_URL` overrides for alternative providers). +- Recommended: Docker Compose (shipped in repo) to run Memgraph, Neo4j, and Redis together when developing locally. Additional + targeted stacks live under `meshmind/tests/docker/` for integration tests. + +## Environment Variables +- `GRAPH_BACKEND` — `memory`, `sqlite`, `memgraph`, or `neo4j` (defaults to `memory`). +- `LLM_API_KEY` / `OPENAI_API_KEY` — required for extraction, embeddings, and reranking (OpenAI-compatible providers). +- `LLM_DEFAULT_MODEL` / `LLM_DEFAULT_BASE_URL` — default Responses model/base URL applied when overrides are absent. +- `LLM_EXTRACTION_MODEL` / `LLM_EXTRACTION_BASE_URL` — optional overrides for extraction calls. +- `LLM_EMBEDDING_MODEL` / `LLM_EMBEDDING_BASE_URL` — optional overrides for embedding requests. +- `LLM_RERANK_MODEL` / `LLM_RERANK_BASE_URL` — optional overrides for reranking requests. +- `MEMGRAPH_URI` — e.g., `bolt://localhost:7687` (when using Memgraph). +- `MEMGRAPH_USERNAME` and `MEMGRAPH_PASSWORD` — credentials for the Memgraph database. +- `NEO4J_URI`, `NEO4J_USERNAME`, `NEO4J_PASSWORD` — optional Neo4j connectivity details. +- `SQLITE_PATH` — filesystem path for the SQLite graph backend (defaults to in-memory). +- `REDIS_URL` — optional Redis connection URI (defaults to `redis://localhost:6379/0`). +- `EMBEDDING_MODEL` — legacy alias for the embedding encoder key (defaults to `LLM_EMBEDDING_MODEL`). +- `MAINTENANCE_MAX_ATTEMPTS` — retry attempts for consolidation/compression writes (defaults to `3`). +- `MAINTENANCE_BASE_DELAY_SECONDS` — base delay used for exponential backoff (defaults to `1.0`). +- Optional overrides for Celery broker/backend if using hosted services. + +## Local Configuration Steps +- Ensure an embedding encoder is registered before extraction or hybrid search. The bootstrap utilities invoked by the CLI and + `MeshMind` constructor handle this, but custom scripts must call `bootstrap_encoders()`. +- For REST/gRPC testing, instantiate the `RestAPIStub`/`GrpcServiceStub` with the in-memory driver to avoid external services. +- Use `meshmind/testing` fakes (`FakeMemgraphDriver`, `FakeRedisBroker`, `FakeEmbeddingEncoder`, `FakeLLMClient`) in tests or demos to eliminate external infrastructure requirements. +- Invoke `meshmind admin predicates` and `meshmind admin maintenance --max-attempts --base-delay --run ` during local runs to inspect predicate registries, telemetry, and tune maintenance retries without external services. +- Use the benchmarking utilities in `scripts/` (`evaluate_importance.py`, `consolidation_benchmark.py`, `benchmark_pagination.py`) to validate heuristics and driver performance offline before connecting to live infrastructure. +- Seed demo data as needed using the `examples/extract_preprocess_store_example.py` script after configuring environment + variables. +- Create a `.env` file storing the environment variables above for consistent local configuration. + +## Current Blockers in This Environment +- Neo4j/Memgraph binaries and Docker are unavailable in this workspace, preventing local graph provisioning; use the in-memory or SQLite drivers instead. +- Redis cannot be installed without container or host-level access; Celery tasks remain untestable locally until a remote + instance is provisioned (the fake broker satisfies unit tests but not end-to-end runs). +- External network restrictions may limit installation of proprietary packages or access to OpenAI endpoints. diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..9010f9f --- /dev/null +++ b/PLAN.md @@ -0,0 +1,48 @@ +# Plan of Action + +## Phase 1 – Stabilize Runtime Basics ✅ +1. **Dependency Guards** – Implemented lazy driver factories, optional imports, and clear ImportErrors for missing packages. +2. **Default Encoder Registration** – Bootstraps register encoders/entities automatically and the CLI invokes them on startup. +3. **OpenAI SDK Compatibility** – Extraction and embedding adapters align with the Responses API; remaining polish tracked in + `ISSUES.md`. +4. **Configuration Clarity** – `README.md`, `ENVIRONMENT_NEEDS.md`, and the new `docs/` pages document environment variables and service setup. + +## Phase 2 – Restore Promised API Surface ✅ +1. **Entity & Predicate Registry Wiring** – `MeshMind` now boots registries and storage persists predicates automatically. +2. **CRUD & Triplet Support** – CRUD helpers and triplet APIs live on `MeshMind` and `MemoryManager`, storing relationships via + `GraphDriver.upsert_edge`. +3. **Relationship-Aware Examples** – Updated example script demonstrates triplet creation and retrieval flows. + +## Phase 3 – Retrieval & Maintenance Enhancements (In Progress) +1. **Search Coverage** – Hybrid, vector-only, regex, exact-match, fuzzy, and LLM rerank helpers are implemented and exposed. + Graph-backed wrappers now rely on driver-side filtering, pagination, and aggregation before in-memory scoring. Next: push + similarity computation into Memgraph/Neo4j so vector rankings can execute server-side without Python hydration. +2. **Maintenance Tasks** – Tasks emit telemetry, persist consolidation/compression results, and now retry conflicting writes with + configurable exponential backoff (`MAINTENANCE_MAX_ATTEMPTS`, `MAINTENANCE_BASE_DELAY_SECONDS`). Synthetic benchmark scripts and + large-fixture tests validate behaviour on bigger workloads; next, replay production-like datasets to tune thresholds. +3. **Importance Scoring Improvements** – Heuristic scoring is live, records distribution metrics via telemetry, and ships with + `scripts/evaluate_importance.py` for synthetic/offline evaluation. Next: incorporate real feedback loops or LLM-assisted + ranking to tune weights over time. +4. **LLM Provider Flexibility** – Direct `openai` usage has been consolidated behind `meshmind.llm_client`. The wrapper + honors `LLM_*` environment variables, CLI flags, and REST/gRPC payload overrides (`use_llm_rerank`, `llm_models`, + `llm_base_urls`, `llm_api_key`, `rerank_model`). Timestamp utilities have been standardized on timezone-aware UTC defaults. + Next: validate live provider integrations once external credentials and network routing are provisioned. + +## Phase 4 – Developer Experience & Tooling (In Progress) +1. **Testing Overhaul** – Pytest suites rely on local fixtures and fake drivers with coverage for graph-backed retrieval, Neo4j + connectivity shims, CLI admin helpers, documentation guard, setup scripts, and the new benchmarking utilities. Continue adding + cross-backend integration coverage and track shim retirement progress in `DUMMIES.md` so integration suites can replace them + incrementally. +2. **Automation & CI** – Makefile provides lint/format/type/test/docs-guard targets and CI runs fmt-check, docs guard, and + pytest. Protobuf drift now fails CI via `make protos-check`. Add caching and matrix builds when dependencies stabilize. +3. **Environment Provisioning** – Docker Compose now provisions Memgraph, Neo4j, and Redis (with targeted stacks for tests). + Track multi-backend examples, document the new `SETUP.md`, keep docs current, and distribute the new `run/install_setup.sh` + and `run/maintenance_setup.sh` automation scripts for environment bootstrap. + +## Phase 5 – Strategic Enhancements (Planned) +1. **Graph-Backed Retrieval** – Extend the new driver-side filtering/pagination to full vector/lexical execution using backend-native indexes to avoid round-tripping candidate embeddings. +2. **Operational Observability** – Export telemetry to Prometheus/OpenTelemetry and surface dashboards/alerts. +3. **Celery Hardening** – Stress test consolidation/compression heuristics at scale and codify retry/backoff policies. +4. **Service Contracts** – Generated protobuf modules (`meshmind/protos/memory_service.proto`) back both the Python stub and the + new asyncio gRPC server helpers. Next: package a deployable entry point, publish generated clients, and add integration tests + once infrastructure is ready. diff --git a/PLANNING_THOUGHTS.md b/PLANNING_THOUGHTS.md new file mode 100644 index 0000000..4dfc823 --- /dev/null +++ b/PLANNING_THOUGHTS.md @@ -0,0 +1,19 @@ +# Planning Thoughts + +## Strategic Questions +- How aggressively should we lean into backend-native vector search versus maintaining Python-side fallbacks for portability? +- What criteria will determine when compatibility shims (Pydantic, FastAPI, FakeLLM) can be retired without regressing developer workflows? +- Which observability stack (Prometheus, OpenTelemetry Collector, custom logs) best aligns with target deployments and hosting environments? +- How do we prioritise between accuracy improvements (LLM rerank, heuristics) and operational scalability (streaming, pagination) given limited engineering capacity? + +## Decision Log Candidates +- **Graph Backend Selection** – Document pros/cons of SQLite for local development versus Neo4j/Memgraph for production to guide onboarding. +- **LLM Provider Strategy** – Track evaluation results for OpenAI-compatible providers (OpenRouter, Google) and capture failover requirements. +- **Maintenance Scheduling** – Record chosen cadence, concurrency, and backoff defaults once consolidation heuristics are validated at scale. +- **Schema Governance** – Capture conventions for namespaces, entity labels, and predicate registries so ingestion pipelines stay consistent. + +## Upcoming Research +- Benchmark consolidation heuristics on synthetic datasets representing customer scale and capture telemetry snapshots. +- Compare graph query latency across in-memory, SQLite, Memgraph, and Neo4j drivers when using pagination and filtering. +- Evaluate rerank quality across LLM providers using a labelled evaluation set to determine optimal default models. +- Investigate options for secure secret storage (e.g., Vault, AWS Secrets Manager) to standardise API key management. diff --git a/PROJECT.md b/PROJECT.md new file mode 100644 index 0000000..ac69a3e --- /dev/null +++ b/PROJECT.md @@ -0,0 +1,93 @@ +# MeshMind Project Overview + +## Vision and Scope +- Transform unstructured text into graph-backed `Memory` records enriched with embeddings and metadata. +- Offer pipelines for extraction, preprocessing, storage, and retrieval that can be orchestrated from CLI tools or bespoke agents. +- Enable background maintenance workflows (expiry, consolidation, compression) once supporting services are provisioned. + +## Current Architecture Snapshot +- **Client façade**: `meshmind.client.MeshMind` composes a provider-agnostic LLM client, configurable embedding model, registry + bootstrap, and a lazily created graph driver selected via `GRAPH_BACKEND` (memory, SQLite, Memgraph, Neo4j). Retrieval helpers expose + hybrid, vector, regex, exact, and reranked flows. +- **Pipelines**: Extraction (LLM + function calling), preprocessing (deduplicate, score, compress), and storage utilities live in + `meshmind.pipeline`. Maintenance helpers consolidate duplicates and expire stale memories. +- **Graph layer**: `meshmind.db` defines `GraphDriver` and implements in-memory, SQLite, Memgraph, and optional Neo4j drivers + with a shared factory for local testing and production use. +- **Retrieval helpers**: `meshmind.retrieval` now covers BM25, fuzzy, hybrid, vector-only, regex, exact-match, and LLM rerank + workflows with shared filters, reranker utilities, and driver-side filtering/pagination to minimise Python hydration. +- **Task runners**: `meshmind.tasks` configures Celery beat to run expiry, consolidation, and compression. Drivers/managers + initialize lazily so import-time failures are avoided. +- **Support code**: `meshmind.core` provides configuration, data models, embeddings, similarity math, and optional dependency + guards around tokenization. +- **Service adapters**: `meshmind.api.rest` and `.grpc` expose REST/gRPC entry points. The gRPC surface now uses generated + protobuf messages (`meshmind/protos/memory_service.proto`) and runtime helpers in `meshmind.api.grpc_server` so tests, + scripts, and deployments share a canonical schema while retaining the in-process stub for fast feedback. +- **Observability**: `meshmind.core.observability` collects metrics, gauges, and structured log events across pipelines and + scheduled tasks. +- **Tooling**: The CLI ingest command (`meshmind ingest`), updated example script, Makefile automation, CI workflow, and Docker + Compose file illustrate extraction → preprocessing → storage → retrieval locally. +- **Test doubles**: `meshmind/testing` provides fake Memgraph, Redis, embedding, and LLM drivers for offline test runs while production code now depends on first-party Pydantic models. + +## Implemented Capabilities +- Serialize knowledge as `Memory` (nodes) and `Triplet` (relationships) Pydantic models with namespaces, metadata, embeddings, + timezone-aware timestamps, TTL, and importance fields. +- Extract structured memories from text via the provider-agnostic `LLMClient` (Responses API compatible), with encoder registration handled during bootstrap. +- Deduplicate memories by name and cosine similarity, score importance heuristically (token diversity, recency, metadata, embedding magnitude), and compress metadata when `tiktoken` is installed. +- Persist memory nodes and triplet relationships through the storage pipeline and `MemoryManager` CRUD helpers. +- Configure LLM providers globally via `LLM_*` environment variables while supporting per-request overrides through CLI flags, + REST/gRPC payloads, and the shared `LLMClient` wrapper. +- Perform hybrid, vector-only, regex, exact-match, fuzzy, and BM25 retrieval with optional metadata filters and LLM reranking, leveraging driver-side filtering/pagination to shrink result sets before scoring. +- Summarize stored memories by namespace/entity label via the CLI (`meshmind admin counts`) and REST `/memories/counts` route. +- Provide CRUD surfaces on `MeshMind` for creating, updating, deleting, and listing memories and triplets. +- Run Celery maintenance tasks (expiry, consolidation, compression) that tolerate missing graph drivers until runtime and persist consolidated/compressed memories back to the selected backend. +- Demonstrate ingestion, relationship creation, and retrieval in `examples/extract_preprocess_store_example.py`. +- Automate linting, formatting, type checking, and testing through the Makefile and GitHub Actions. + +## Partially Implemented or Fragile Areas +- The LLM-backed embedding wrapper still assumes dictionary-style responses; adjust once SDK models are fully adopted. +- Neo4j driver support is import-guarded; the new CLI connectivity check still needs validation against a live cluster. +- Maintenance tasks rely on in-process heuristics for consolidation summaries; conflict resolution now retries with configurable exponential backoff, but long-term storage thresholds still need validation against production datasets. +- Importance scoring now records telemetry but still relies on heuristics; richer scoring logic or LLM-assisted ranking is pending. +- SQLite driver currently stores JSON blobs; future work may normalize columns for structured querying. + +## Missing or Broken Capabilities +- Graph-backed retrieval still hydrates namespace/entity-label filtered candidates client-side; pushing ranking into the graph store is future work. +- Predicate management remains internal to the bootstrap process; external administration APIs are still missing. +- Metrics remain in-memory; external exporters (Prometheus/OpenTelemetry) are not wired up. +- gRPC deployments now rely on the asyncio server helpers; packaging an executable entry point and adding end-to-end integration + tests remain future work. + +## External Services & Dependencies +- **Graph backend**: Choose via `GRAPH_BACKEND`. In-memory and SQLite require no external services. Memgraph needs the `pymgclient` package (which exposes the `mgclient` module); + Neo4j requires the official driver and a live instance. +- **LLM providers**: Install the OpenAI SDK (or compatible fork) for extraction, embeddings, and reranking; configure `LLM_API_KEY` (or fallback `OPENAI_API_KEY`) and the `LLM_*` model/base URL overrides as needed. +- **tiktoken**: Optional but necessary for compression/token budgeting. +- **RapidFuzz, scikit-learn, numpy**: Support fuzzy and lexical retrieval. +- **Celery + Redis**: Optional but necessary for scheduled maintenance jobs. +- **sentence-transformers**: Optional embedding backend for offline models. +- **ruff, pyright, typeguard, toml-sort, yamllint**: Development tooling invoked by the Makefile and CI workflow. + +## Tooling and Operational State +- `Makefile` exposes `install`, `lint`, `fmt`, `fmt-check`, `typecheck`, `test`, `check`, `docs-guard`, `docker`, and `clean` + targets. `make install` installs the `.[dev,docs,testing]` extras so optional dependencies are present. +- `.github/workflows/ci.yml` runs formatting/linting checks, the documentation guard, and pytest on push and pull requests. +- Tests rely on fixtures (`memory_factory`, `dummy_encoder`, in-memory drivers`) so they pass without external services, though installing optional dependencies improves fidelity. +- Benchmark and evaluation utilities live in `scripts/` (`evaluate_importance.py`, `consolidation_benchmark.py`, `benchmark_pagination.py`) to validate heuristics and driver performance without external infrastructure. +- Developer-facing documentation now lives in `docs/` alongside the canonical `README.md`; the docs guard (`make docs-guard`) enforces synchronized updates when modules change. +- Docker Compose now provisions Memgraph, Neo4j, and Redis; integration-specific stacks (including the Celery worker) live under + `meshmind/tests/docker/`. See `ENVIRONMENT_NEEDS.md` and `SETUP.md` for enabling optional services locally. + +## Roadmap Highlights +- Push graph-backed retrieval deeper into the drivers (vector similarity, structured filters) so the new server-side filtering/pagination evolves into full backend-native ranking. +- Export observability metrics to external sinks (Prometheus/OpenTelemetry) and surface dashboards. +- Enhance importance scoring with data-driven heuristics or LLM evaluation. +- Validate consolidation heuristics and conflict-resolution rules against real datasets. +- Validate Neo4j driver behaviour against a live cluster and ship official test doubles for Memgraph/Redis/encoders. +- Continue refining documentation to reflect setup, troubleshooting, and architectural decisions. +- Reintroduce full Pydantic models once dependency availability is guaranteed in target environments. + +## Future Potential Extensions +- Plugin-based encoder and retriever registration for runtime extensibility. +- Streaming ingestion workers (queues, webhooks) beyond batch CLI workflows. +- UI or agent-facing dashboards for curation, monitoring, and analytics. +- Automated CI pipelines for release packaging, schema migrations, and integration tests. diff --git a/README.md b/README.md index 04882e7..868f949 100644 --- a/README.md +++ b/README.md @@ -1,244 +1,281 @@ # MeshMind -MeshMind is a knowledge management system that uses LLMs and graph databases to store and retrieve information. -Adding, Searching, Updating, and Deleting memories is supported. -Retrieval of memories using LLMs and graph databases by comparing embeddings and graph database metadata. - -## Features - -- Adding memories -- Searching memories -- Updating memories -- Deleting memories -- Extracting memories from content -- Expiring memories -- Memory Importance Ranking -- Memory Deduplication -- Memory Consolidation -- Memory Compression - -## Retrieval Methods - -- Embedding Vector Search -- BM25 Retrieval -- ReRanking with LLM -- Fuzzy Search -- Exact Comparison Search -- Regex Search -- Search Filters -- Hybrid Search Methods - -## Components -- OpenAI API -- MemGraphDB (Alternative to Neo4j) -- Embedding Model - -# Types of Memory - -- Long-Term Memory - Persistent Memory - - Explicit Memory - Conscious Memory - Active Hotpath Memory (Triggered in Response to Input) - - Declarative Memory - Conscious Memory - - Semantic Memory - What is known - - Eposodic Memory - What has been experienced - - Procedural Memory - How to perform tasks - - - Implicit Memory - Subconscious Memory - Background Process Memory (Triggered in Intervals) - - Non-Declarative Memory - Subconscious Memory - - Semantic Memory - Implicitly acquired - - Eposodic Memory - Implicitly acquired - - Procedural Memory - Implicitly acquired - -- Short-Term Memory - Transient Memory - - Working Memory (Processing) [reasoning messages, scratchpad, etc] - - Sensory Memory (Input) [user input, system input, etc] - - Log Storage (Output) [assistant responses, tool logs, etc] - -# Methods -- Extract Memory -- Add Memory -- Add Triplet -- Search Memory -- Update Memory -- Delete Memory - -## Usage - +MeshMind is an experimental memory orchestration service that pairs large language models with a property graph. It extracts +structured `Memory` records from unstructured text, enriches them with embeddings and metadata, and stores both nodes and +relationships via a Memgraph driver. Retrieval helpers operate on in-memory collections today, offering hybrid, vector-only, +regex, exact-match, fuzzy, and BM25 scoring with optional LLM reranking. + +## Status at a Glance +- ✅ `meshmind.client.MeshMind` orchestrates extraction, preprocessing, storage, CRUD helpers, and retrieval wrappers. +- ✅ Pipelines deduplicate memories, score importance with token/recency/metadata heuristics, compress metadata, and persist nodes and triplets. +- ✅ Retrieval helpers expose hybrid, vector-only, regex, exact-match, BM25, fuzzy, and rerank workflows with namespace/entity + filters. +- ✅ Celery tasks for expiry, consolidation, and compression initialize lazily and run when Redis and Memgraph are configured. +- ✅ Makefile and GitHub Actions provide linting, formatting, type checking, and pytest automation. +- ✅ Docker Compose provisions Memgraph, Neo4j, and Redis for local orchestration, with + integration-specific stacks under `meshmind/tests/docker/` for Celery workers. +- ✅ Built-in observability surfaces structured events and in-memory metrics for pipelines and scheduled tasks while Celery consolidation/compression flows now persist their updates. +- ✅ Native Pydantic 2.x models and fake drivers let the suite run without scikit-learn, rapidfuzz, Redis, or live Memgraph instances. +- ✅ Graph-backed retrieval wrappers load memories directly from the configured driver when collections are omitted. +- ✅ Graph drivers filter by namespace and entity label before hydrating candidates, keeping hybrid searches efficient on large graphs. + +## Requirements +- Python 3.11 or 3.12 recommended (`pyproject.toml` pins `>=3.11,<3.13` while third-party packages catch up). +- Configurable graph backend via `GRAPH_BACKEND` (`memory`, `sqlite`, `memgraph`, `neo4j`). +- Memgraph instance reachable via Bolt and the `pymgclient` Python package (which exposes the `mgclient` module for + `GRAPH_BACKEND=memgraph`). +- Optional Neo4j instance with the official Python driver (when `GRAPH_BACKEND=neo4j`). +- OpenAI-compatible API key (set `LLM_API_KEY` or `OPENAI_API_KEY`) for extraction, embeddings, and optional reranking. +- Optional: Redis and Celery for scheduled maintenance tasks. +- Optional: `numpy`, `scikit-learn`, and `rapidfuzz` improve similarity and lexical matching but pure-Python fallbacks are bundled. +- Install project dependencies with `pip install -e .[dev,docs,testing]`; see `SETUP.md` for a detailed walkthrough. + +## Installation +> **Tip:** For automated provisioning with internet access, run `./run/install_setup.sh` from the project root (requires sudo). + +1. Create and activate a virtual environment using Python 3.11/3.12 (e.g., `uv venv`, `python -m venv .venv`). +2. Upgrade `pip` and install MeshMind with all optional extras: + ```bash + python -m pip install --upgrade pip + pip install uv + uv pip install --system -e .[dev,docs,testing] # drop --system if you're inside a virtualenv + ``` +3. Export required environment variables (or populate `.env`; see `SETUP.md`): + ```bash + export OPENAI_API_KEY=sk-... + export LLM_API_KEY=${LLM_API_KEY:-$OPENAI_API_KEY} + export LLM_DEFAULT_MODEL=gpt-5-nano + export LLM_DEFAULT_BASE_URL=https://api.openai.com/v1 + export LLM_EXTRACTION_MODEL=gpt-5-nano + export LLM_EXTRACTION_BASE_URL= + export LLM_EMBEDDING_MODEL=text-embedding-3-small + export LLM_EMBEDDING_BASE_URL= + export LLM_RERANK_MODEL=gpt-5-nano + export LLM_RERANK_BASE_URL= + export GRAPH_BACKEND=memory # or memgraph/sqlite/neo4j + export MEMGRAPH_URI=bolt://localhost:7687 + export MEMGRAPH_USERNAME= # optional; Memgraph defaults to anonymous auth + export MEMGRAPH_PASSWORD= + export SQLITE_PATH=/tmp/meshmind.db + export NEO4J_URI=bolt://localhost:7688 + export NEO4J_USERNAME=neo4j + export NEO4J_PASSWORD=meshminD123 + export REDIS_URL=redis://localhost:6379/0 + export MAINTENANCE_MAX_ATTEMPTS=3 + export MAINTENANCE_BASE_DELAY_SECONDS=1.0 + export EMBEDDING_MODEL=${LLM_EMBEDDING_MODEL} + ``` + +4. Provision Redis, Memgraph, and Neo4j with Docker Compose when you need external + services: + ```bash + docker compose up -d + ``` + Alternate topologies live in `meshmind/tests/docker/`; see `SETUP.md` for guidance on + targeted stacks and teardown commands. + +## Encoder Registration +`MeshMind` bootstraps encoders and entities during initialization, but custom scripts can register additional encoders: ```python -from meshmind import MeshMind -from pydantic import BaseModel, Field - -# Pydantic model's name will be used as the node label in the graph database. -# This defines the attributes of the entity. -# Attributes of the entity are stored in entity metadata['attributes'] -class Person(BaseModel): - first_name: str | None = Field(..., description="First name of the person") - last_name: str | None = Field(None, description="Last name of the person") - description: str | None = Field(None, description="Description of the person") - job_title: str | None = Field(None, description="Job title of the person") - -# Initialize MeshMind -mesh_mind = MeshMind() - -# Register pydantic model as entity -mesh_mind.register_entity(Person) - -# Register allowed relationship predicates labels. -mesh_mind.register_allowed_predicates([ - "employee_of", - "on_team", - "on_project", -]) - -# Add edge - This will create an edge in the graph database. (Alternative to `register_allowed_types`) -mesh_mind.add_predicate("has_skill") - -# Extract memories - [High Level API] - This will create nodes and edges based on the content provided. Extracts memories from the content. -extracted_memories = mesh_mind.extract_memories( - instructions="Extract all memories from the database.", - namespace="Company Employees", - entity_types=[Person], - content=["John Doe, Software Engineer 10 years of experience.", "Jane Doe, Software Engineer 5 years of experience."] - ) +from meshmind.core.embeddings import EncoderRegistry, OpenAIEmbeddingEncoder -for memory in extracted_memories: - # Store memory - This will perform deduplication, add uuid, add timestamps, format memory and store the memory in the graph database using `add_triplet`. - mesh_mind.store_memory(memory) - -# Add memory - [Mid Level API] - This will perform all preprocessing steps i.e. deduplication, add uuid, add timestamps, format memory, etc. and store the memory in the graph database using `add_triplet`. ( Skips extraction and automatically adds memory to the graph database ) **Useful for adding custom memories** -mesh_mind.add_memory( - namespace="Company Employees", - name="John Doe", - entity_label="Person", - entity=Person( - first_name="John", - last_name="Doe", - description="John Doe", - job_title="Software Engineer", - ), - metadata={ - "source": "John Doe Employee Record", - "source_type": "text", - } -) +if not EncoderRegistry.is_registered("text-embedding-3-small"): + EncoderRegistry.register("text-embedding-3-small", OpenAIEmbeddingEncoder("text-embedding-3-small")) +``` +You may register deterministic or local encoders (e.g., sentence-transformers) for offline testing. The +`OpenAIEmbeddingEncoder` now delegates to the provider-agnostic `meshmind.llm_client.LLMClient`, so any +OpenAI-compatible endpoint can serve embeddings once configured via environment variables or CLI overrides. -# `add_triplet` - [Low Level API] -# -# Add a [node, edge, node] triplet - This will create a pair of nodes and a connecting edge in the graph database using db driver. -# This bypasses deduplication and other preprocessing steps and directly adds the data to the graph database. -# This is useful for adding data that is not in the format of a memory. -# -# subject: The subject of the triplet. ( Source Entity Node Label Name ) -# predicate: The predicate of the triplet. ( Relationship between the subject and object, registered using `register_allowed_predicates` ) -# object: The object of the triplet. ( Target Entity Node Label Name ) -# namespace: The namespace of the triplet. ( Group of related nodes and edges ) -# entity_label: The label of the entity. ( Type of node, registered using `register_entity` ) -# metadata: The metadata of the triplet. ( Additional information about the triplet ) -# reference_time: The time at which the triplet was created. ( Optional ) -# -# If the subject, predicate, object, namespace, or entity_label does not exist, it will be created. -# -# Example: -mesh_mind.add_triplet( - subject="John Doe", - predicate="on_project", - object="Project X", - namespace="Company Employees", - entity_label="Person", - metadata={ - "source": "John Doe Project Record", - "source_type": "text", - "summary": "John Doe is on project X.", - "attributes": { - "first_name": "John", - "last_name": "Doe", - "description": "John Doe", - "job_title": "Software Engineer", - } - }, - reference_time="2025-05-09T23:31:51-04:00" +## Quick Start +```python +from meshmind.client import MeshMind +from meshmind.core.types import Memory, Triplet + +mm = MeshMind() # Uses LLM defaults from LLM_* environment variables +texts = ["Python is a programming language created by Guido van Rossum."] +memories = mm.extract_memories( + instructions="Extract key facts as Memory objects.", + namespace="demo", + entity_types=[Memory], + content=texts, ) - -# Search - [High Level API] - This will search the graph database for nodes and edges based on the query. -search_results = mesh_mind.search( - query="John Doe", - namespace="Company Employees", - entity_types=[Person], +memories = mm.deduplicate(memories) +memories = mm.score_importance(memories) +memories = mm.compress(memories) +mm.store_memories(memories) + +if len(memories) >= 2: + relation = Triplet( + subject=str(memories[0].uuid), + predicate="RELATED_TO", + object=str(memories[1].uuid), + namespace="demo", + entity_label="Knowledge", ) + mm.store_triplets([relation]) +``` -for search_result in search_results: - print(search_result) - -# Search - [Mid Level API] - This will search the graph database for nodes and edges based on the query. -search_results = mesh_mind.search_facts( - query="John Doe", - namespace="Company Employees", - entity_types=[Person], - config=SearchConfig( - encoder="text-embedding-3-small", - ) - ) +## Retrieval +`MeshMind` exposes multiple retrieval helpers that operate on lists of `Memory` objects (e.g., fetched via +`mm.list_memories(namespace="demo")`). When you omit the `memories` argument, the client fetches candidates directly from the +active graph backend configured through `GRAPH_BACKEND` or the driver supplied to `MeshMind`. Driver-backed searches now use +server-side filtering (`query`, `entity_labels`) and pagination (`offset`, `limit`) before in-memory scoring to avoid loading +entire namespaces. +```python +from meshmind.core.types import SearchConfig -for search_result in search_results: - print(search_result) - -# Search - [Low Level API] - This will search the graph database for nodes and edges based on the query. -search_results = mesh_mind.search_procedures( - query="John Doe", - namespace="Company Employees", - entity_types=[Person], - config=SearchConfig( - encoder="text-embedding-3-small", - ) - ) +memories = mm.list_memories(namespace="demo", entity_labels=["Knowledge"], limit=25) +config = SearchConfig(encoder=mm.embedding_model, top_k=5, rerank_model="gpt-5-nano") -# Update Memory - Same as `add_memory` but updates an existing memory. -mesh_mind.update_memory( - uuid="12345678-1234-1234-1234-123456789012", - namespace="Company Employees", - name="John Doe", - entity_label="Person", - entity=Person( - first_name="John", - last_name="Doe", - description="John Doe", - job_title="Software Engineer", - ), - metadata={ - "source": "John Doe Employee Record", - "source_type": "text", - } -) +hybrid = mm.search("Python", namespace="demo", entity_labels=["Knowledge"], config=config, use_llm_rerank=True) +vector_only = mm.search_vector("programming", namespace="demo", entity_labels=["Knowledge"]) +regex_hits = mm.search_regex(r"Guido", namespace="demo", entity_labels=["Knowledge"]) +exact_hits = mm.search_exact("Python", namespace="demo", entity_labels=["Knowledge"]) +``` +The in-memory and graph-backed search helpers support namespace/entity filters and optional reranking via the +provider-agnostic `LLMClient`. Entity-label filters are applied at the driver, avoiding unnecessary hydration when graphs +contain heterogeneous nodes. You can still pass explicit lists (e.g., during testing) to bypass graph access when desired. -# Delete Memory -mesh_mind.delete_memory( - uuid="12345678-1234-1234-1234-123456789012" -) +## Command-Line Operations +```bash +meshmind ingest \ + --namespace demo \ + --instructions "Extract key facts as Memory objects." \ + ./path/to/text/files +``` +The CLI bootstraps encoders/entities automatically. Ensure environment variables are set and Memgraph is reachable. +LLM connectivity is configurable per operation: -for search_result in search_results: - print(search_result) +- `--llm-api-key` and `--llm-base-url` override the provider credentials for the entire run. +- `--extraction-model` / `--extraction-endpoint` adjust the Responses API call that performs entity extraction. +- `--embedding-model` / `--embedding-endpoint` override encoder generation (defaults to `LLM_EMBEDDING_MODEL`). +- `--rerank-model` / `--rerank-endpoint` control reranking when `use_llm_rerank=True` is provided to `MeshMind.search`. -``` +### LLM Override Precedence -## Command-Line Interface (CLI) +The `meshmind.llm_client.LLMClient` merges configuration from multiple sources before issuing +Requests API/Embeddings calls. The precedence order is: -MeshMind includes a `meshmind` CLI tool for ingesting content via the extract → preprocess → store pipeline. +1. **Explicit payload overrides** – REST/gRPC requests may include `llm_models`, `llm_base_urls`, and `llm_api_key` + dictionaries; CLI commands surface equivalent `--extraction-*`, `--embedding-*`, and `--rerank-*` flags. These values + apply to the single request being processed. +2. **Environment variables** – `LLM_*` variables (and the legacy `OPENAI_API_KEY`) define the default provider, model, and + endpoint when no per-request overrides are supplied. +3. **Built-in defaults** – The client falls back to `gpt-5-nano` for Responses calls and `text-embedding-3-small` for + embeddings when neither payload nor environment values exist. -Usage: -```bash -meshmind --help -``` -Primary command: +This layering allows operators to set safe defaults globally while giving automation and human workflows the ability to +experiment with alternative providers on demand. + +Administrative helpers expose predicate registry and telemetry insight: ```bash -meshmind ingest \ - -n \ - [-e ] \ - [-i ""] \ - [ ...] +meshmind admin predicates --list +meshmind admin predicates --add RELATED_TO +meshmind admin maintenance --max-attempts 5 --base-delay 2.5 --run consolidate +meshmind admin graph --backend neo4j +meshmind admin counts --namespace demo ``` -Example: + +## Maintenance Tasks +Celery tasks in `meshmind.tasks.scheduled` provide expiry, consolidation, and compression maintenance with persistence. ```bash -meshmind ingest -n demo --embedding-model text-embedding-3-small ./data/articles +celery -A meshmind.tasks.celery_app.app worker -B ``` -This reads text files under `./data/articles`, extracts memories, deduplicates, scores, compresses, -and stores them in your Memgraph database under the `demo` namespace. - +Tasks instantiate the driver lazily, emit structured logs/metrics, and persist consolidated or compressed memories back to the selected graph driver. Consolidation writes now honour exponential backoff and retry semantics driven by `MAINTENANCE_MAX_ATTEMPTS` and `MAINTENANCE_BASE_DELAY_SECONDS`, logging every conflict and recording telemetry about retry durations. You can override those values before triggering Celery or the on-demand CLI by supplying `meshmind admin maintenance --max-attempts ... --base-delay ... --run `. Provide valid environment variables and ensure Memgraph/Redis are running when using external backends. + +## Tooling +- **Makefile** – `make fmt`, `make lint`, `make typecheck`, `make test`, `make check`, `make docker`, `make clean`, + `make docs-guard`. +- **CI** – `.github/workflows/ci.yml` runs formatting checks (ruff, toml-sort, yamllint), pytest, and the documentation guard + to ensure code changes keep the wiki up to date. +- **Examples** – `examples/extract_preprocess_store_example.py` demonstrates ingestion, triplet creation, and multiple retrieval + strategies. +- **Dockerfile / docker-compose** – Container definition and orchestration files that provision Memgraph, Neo4j, Redis, and the + Celery worker stacks documented in `SETUP.md` and `meshmind/tests/docker/`. +- **Provisioning scripts** – `run/install_setup.sh` and `run/maintenance_setup.sh` validate that optional packages (`fastapi`, + `neo4j`, `pymgclient`, `uvicorn`) are present and respect `MESH_SKIP_SYSTEM_PACKAGES=1` / `MESH_SKIP_PYTHON_SYNC=1` when you + need a dry run without network access. + +## Benchmarking & Evaluation +- **Importance scoring** – `scripts/evaluate_importance.py` runs the heuristic against JSON or synthetic datasets and reports + descriptive statistics for quick regression checks. +- **Consolidation throughput** – `scripts/consolidation_benchmark.py` generates synthetic workloads to measure batch merging + duration, skipped groups, and retry behaviour across configuration options. +- **Driver pagination** – `scripts/benchmark_pagination.py` seeds synthetic memories into the selected backend and records + list latency by page size so operators can tune defaults before production rollouts. + Use `make benchmarks` to run all three scripts with synthetic defaults and capture JSON summaries under `build/benchmarks/`. + +## Service Interfaces +- **REST** – `meshmind.api.rest.create_app` returns a FastAPI app (or lightweight stub) that exposes `/memories`, `/triplets`, + `/search`, and `/memories/counts` endpoints. Search payloads accept: + - `use_llm_rerank` to toggle LLM-based reranking, + - `llm_models`, `llm_base_urls`, and `llm_api_key` dictionaries for per-request overrides, + - `rerank_model` when you need to pin the reranker explicitly. + Example: +- ```json + { + "query": "architecture", + "namespace": "demo", + "entity_labels": ["Memory"], + "top_k": 5, + "use_llm_rerank": true, + "llm_models": {"rerank": "openrouter/reranker-v1"}, + "llm_base_urls": {"rerank": "https://openrouter.ai/api/v1"} + } + ``` + Equivalent `curl` invocations against a local FastAPI instance: + ```bash + curl -s -X POST http://localhost:8000/search \ + -H "Content-Type: application/json" \ + -d '{"query":"architecture","namespace":"demo","entity_labels":["Memory"],"top_k":5}' + curl -s "http://localhost:8000/memories/counts?namespace=demo" + ``` +- **gRPC** – `meshmind/protos/memory_service.proto` defines the canonical schema and generates the Python modules consumed by + `meshmind.api.grpc.GrpcServiceStub` and the runtime helpers in `meshmind.api.grpc_server`. Call + `meshmind.api.grpc_server.serve_forever(memory_service, host="0.0.0.0", port=50051)` to launch a production server (for + example, wire the helper into a `uvicorn`-style entry point after constructing a `MemoryService`). The generated + `SearchPayload` message carries the same LLM override fields as the REST payload. To exercise a running gRPC server, use: + ```bash + grpcurl -plaintext -d '{"namespace":"demo"}' localhost:50051 meshmind.api.MemoryService/MemoryCounts + ``` +- **CLI** – `meshmind admin counts` proxies the new driver aggregation helper so operators can audit namespace/entity totals. + Automated smoke tests cover the REST `/memories/counts` route and the CLI command using the in-memory driver, ensuring + documentation snippets stay aligned with the live interface. + +## Developer Documentation +- `docs/overview.md` – high-level module map. +- `docs/persistence.md` – graph driver behaviours and configuration. +- `docs/retrieval.md` – search strategies and entity-label filtering semantics. +- `docs/pipelines.md` – ingestion lifecycle. +- `docs/api.md` – REST/gRPC payloads and CLI expectations. +- `docs/testing.md` – pytest layout and fake drivers. +- `docs/configuration.md` – environment variables and defaults. +- `docs/operations.md` & `docs/telemetry.md` – operational workflows and observability guidance. +- `docs/development.md` – contribution workflow and coding standards. +- `DUMMIES.md` – inventory of temporary shims, fakes, and compatibility layers to retire now that full dependencies are + available. + +## Observability +- `meshmind.core.observability.telemetry` collects counters, gauges, and durations for pipelines and Celery tasks. +- `meshmind.core.observability.log_event` emits structured log messages that annotate pipeline progress. +- Metrics remain in-memory today; export hooks (Prometheus, OpenTelemetry) are future enhancements. + +## Test Doubles & Offline Helpers +- MeshMind now depends on first-party Pydantic 2.x models; the legacy compatibility shim has been removed. +- `meshmind/retrieval/bm25.py`, `meshmind/retrieval/fuzzy.py`, and `meshmind/core/similarity.py` include pure-Python fallbacks for scikit-learn, rapidfuzz, and numpy. +- `meshmind/testing` exports fake Memgraph, Redis, and embedding drivers that power the pytest suite and examples without external infrastructure. +- `DUMMIES.md` lists every remaining stub (REST/gRPC service adapters, Celery fallbacks, compatibility layers) with guidance + on whether to remove or preserve them once external services are provisioned. + +## Testing +- Run `pytest` to execute the suite; tests rely on fixtures and fake drivers so they do not require external services or optional libraries. +- `make typecheck` invokes `pyright` and `typeguard`; install the tooling listed above beforehand. +- See `ENVIRONMENT_NEEDS.md` for environment requirements and known blockers (Docker/Memgraph/Redis availability). + +## Known Limitations +- Graph-backed retrieval still hydrates candidates client-side (now filtered by namespace and entity label); server-side vector search remains future work. +- Metrics remain in-memory; no external exporter is wired up yet. +- Importance scoring uses heuristics and telemetry but does not yet incorporate feedback loops or LLM-assisted ranking. + +## Roadmap Snapshot +Consult `PROJECT.md`, `PLAN.md`, and `RECOMMENDATIONS.md` for prioritized enhancements: graph-backed retrieval, metrics exporters, richer importance scoring, and production-ready service deployments. diff --git a/README_OLD.md b/README_OLD.md new file mode 100644 index 0000000..e0f86dc --- /dev/null +++ b/README_OLD.md @@ -0,0 +1,170 @@ +# MeshMind + +MeshMind is an experimental memory orchestration service that pairs large language models with a property graph. It extracts +structured `Memory` records from unstructured text, enriches them with embeddings and metadata, and stores both nodes and +relationships via a Memgraph driver. Retrieval helpers operate on in-memory collections today, offering hybrid, vector-only, +regex, exact-match, fuzzy, and BM25 scoring with optional LLM reranking. + +## Status at a Glance +- ✅ `meshmind.client.MeshMind` orchestrates extraction, preprocessing, storage, CRUD helpers, and retrieval wrappers. +- ✅ Pipelines deduplicate memories, score importance with token/recency/metadata heuristics, compress metadata, and persist nodes and triplets. +- ✅ Retrieval helpers expose hybrid, vector-only, regex, exact-match, BM25, fuzzy, and rerank workflows with namespace/entity + filters. +- ✅ Celery tasks for expiry, consolidation, and compression initialize lazily and run when Redis and Memgraph are configured. +- ✅ Makefile and GitHub Actions provide linting, formatting, type checking, and pytest automation. +- ✅ Docker Compose provisions Memgraph, Redis, and a Celery worker for local orchestration. +- ✅ Built-in observability surfaces structured events and in-memory metrics for pipelines and scheduled tasks while Celery consolidation/compression flows now persist their updates. +- ✅ Compatibility shims and fake drivers let the suite run without Pydantic, scikit-learn, rapidfuzz, Redis, or live Memgraph instances. +- ✅ Graph-backed retrieval wrappers load memories directly from the configured driver when collections are omitted. + +## Requirements +- Python 3.11 or 3.12 recommended (`pyproject.toml` pins `>=3.11,<3.13` while third-party packages catch up). +- Configurable graph backend via `GRAPH_BACKEND` (`memory`, `sqlite`, `memgraph`, `neo4j`). +- Memgraph instance reachable via Bolt and the `pymgclient` Python package (which provides the runtime `mgclient` module when + `GRAPH_BACKEND=memgraph`). +- Optional Neo4j instance with the official Python driver (when `GRAPH_BACKEND=neo4j`). +- OpenAI API key for extraction, embeddings, and optional reranking. +- Optional: Redis and Celery for scheduled maintenance tasks. +- Optional: `numpy`, `scikit-learn`, and `rapidfuzz` improve similarity and lexical matching but pure-Python fallbacks are bundled. +- Install project dependencies with `pip install -e .`; see `pyproject.toml` for the full list. + +## Installation +1. Create and activate a virtual environment using Python 3.11/3.12 (e.g., `uv venv`, `python -m venv .venv`). +2. Install MeshMind: + ```bash + pip install -e . + ``` +3. Install optional dependencies as needed: + ```bash + pip install pymgclient tiktoken sentence-transformers celery[redis] ruff pyright typeguard toml-sort yamllint + ``` +4. Export required environment variables: + ```bash + export OPENAI_API_KEY=sk-... + export GRAPH_BACKEND=memory # or memgraph/sqlite/neo4j + export MEMGRAPH_URI=bolt://localhost:7687 + export MEMGRAPH_USERNAME=neo4j + export MEMGRAPH_PASSWORD=secret + export SQLITE_PATH=/tmp/meshmind.db + export NEO4J_URI=bolt://localhost:7687 + export NEO4J_USERNAME=neo4j + export NEO4J_PASSWORD=secret + export REDIS_URL=redis://localhost:6379/0 + export EMBEDDING_MODEL=text-embedding-3-small + ``` + +## Encoder Registration +`MeshMind` bootstraps encoders and entities during initialization, but custom scripts can register additional encoders: +```python +from meshmind.core.embeddings import EncoderRegistry, OpenAIEmbeddingEncoder + +if not EncoderRegistry.is_registered("text-embedding-3-small"): + EncoderRegistry.register("text-embedding-3-small", OpenAIEmbeddingEncoder("text-embedding-3-small")) +``` +You may register deterministic or local encoders (e.g., sentence-transformers) for offline testing. + +## Quick Start +```python +from meshmind.client import MeshMind +from meshmind.core.types import Memory, Triplet + +mm = MeshMind() +texts = ["Python is a programming language created by Guido van Rossum."] +memories = mm.extract_memories( + instructions="Extract key facts as Memory objects.", + namespace="demo", + entity_types=[Memory], + content=texts, +) +memories = mm.deduplicate(memories) +memories = mm.score_importance(memories) +memories = mm.compress(memories) +mm.store_memories(memories) + +if len(memories) >= 2: + relation = Triplet( + subject=str(memories[0].uuid), + predicate="RELATED_TO", + object=str(memories[1].uuid), + namespace="demo", + entity_label="Knowledge", + ) + mm.store_triplets([relation]) +``` + +## Retrieval +`MeshMind` exposes multiple retrieval helpers that operate on lists of `Memory` objects (e.g., fetched via +`mm.list_memories(namespace="demo")`). When you omit the `memories` argument, the client fetches candidates directly from the +active graph backend configured through `GRAPH_BACKEND` or the driver supplied to `MeshMind`. +```python +from meshmind.core.types import SearchConfig + +memories = mm.list_memories(namespace="demo") +config = SearchConfig(encoder=mm.embedding_model, top_k=5, rerank_model="gpt-4o-mini") + +hybrid = mm.search("Python", namespace="demo", config=config, use_llm_rerank=True) +vector_only = mm.search_vector("programming", namespace="demo") +regex_hits = mm.search_regex(r"Guido", namespace="demo") +exact_hits = mm.search_exact("Python", namespace="demo") +``` +The in-memory and graph-backed search helpers support namespace/entity filters and optional reranking via the OpenAI Responses +API. You can still pass explicit lists (e.g., during testing) to bypass graph access when desired. + +## Command-Line Operations +```bash +meshmind ingest \ + --namespace demo \ + --instructions "Extract key facts as Memory objects." \ + ./path/to/text/files +``` +The CLI bootstraps encoders/entities automatically. Ensure environment variables are set and Memgraph is reachable. + +Administrative helpers expose predicate registry and telemetry insight: +```bash +meshmind admin predicates --list +meshmind admin predicates --add RELATED_TO +meshmind admin maintenance +meshmind admin graph --backend neo4j +``` + +## Maintenance Tasks +Celery tasks in `meshmind.tasks.scheduled` provide expiry, consolidation, and compression maintenance with persistence. +```bash +celery -A meshmind.tasks.celery_app.app worker -B +``` +Tasks instantiate the driver lazily, emit structured logs/metrics, and persist consolidated or compressed memories back to the selected graph driver. Provide valid environment variables and ensure Memgraph/Redis are running when using external backends. + +## Tooling +- **Makefile** – `make fmt`, `make lint`, `make typecheck`, `make test`, `make check`, `make docker`, `make clean`. +- **CI** – `.github/workflows/ci.yml` runs formatting checks (ruff, toml-sort, yamllint) and pytest on push/PR. +- **Examples** – `examples/extract_preprocess_store_example.py` demonstrates ingestion, triplet creation, and multiple retrieval + strategies. + +## Service Interfaces +- **REST** – `meshmind.api.rest.create_app` returns a FastAPI app (or lightweight stub) that exposes `/memories`, `/triplets`, + and `/search` endpoints. +- **gRPC** – `meshmind.api.grpc.GrpcServiceStub` mirrors the ingestion and retrieval RPC surface for integration tests and + future server wiring. + +## Observability +- `meshmind.core.observability.telemetry` collects counters, gauges, and durations for pipelines and Celery tasks. +- `meshmind.core.observability.log_event` emits structured log messages that annotate pipeline progress. +- Metrics remain in-memory today; export hooks (Prometheus, OpenTelemetry) are future enhancements. + +## Compatibility & Test Doubles +- `meshmind/_compat/pydantic.py` provides a lightweight `BaseModel` implementation so the codebase functions without installing Pydantic. +- `meshmind/retrieval/bm25.py`, `meshmind/retrieval/fuzzy.py`, and `meshmind/core/similarity.py` include pure-Python fallbacks for scikit-learn, rapidfuzz, and numpy. +- `meshmind/testing` exports fake Memgraph, Redis, and embedding drivers that power the pytest suite and examples without external infrastructure. + +## Testing +- Run `pytest` to execute the suite; tests rely on fixtures, fake drivers, and compatibility shims so they do not require external services or optional libraries. +- `make typecheck` invokes `pyright` and `typeguard`; install the tooling listed above beforehand. +- See `NEEDED_FOR_TESTING.md` for environment requirements and known blockers (Docker/Memgraph/Redis availability). + +## Known Limitations +- Graph-backed retrieval loads namespace-scoped candidates into memory before scoring; server-side vector search remains future work. +- Metrics remain in-memory; no external exporter is wired up yet. +- Importance scoring uses heuristics and telemetry but does not yet incorporate feedback loops or LLM-assisted ranking. + +## Roadmap Snapshot +Consult `PROJECT.md`, `PLAN.md`, and `RECOMMENDATIONS.md` for prioritized enhancements: graph-backed retrieval, metrics exporters, richer importance scoring, and production-ready service deployments. diff --git a/RECOMMENDATIONS.md b/RECOMMENDATIONS.md new file mode 100644 index 0000000..03d977b --- /dev/null +++ b/RECOMMENDATIONS.md @@ -0,0 +1,40 @@ +# Recommendations + +## Stabilize the Foundation +- Maintain lazy initialization for optional dependencies and continue testing environments without Memgraph or LLM provider access. +- Maintain declared Python support at `>=3.11,<3.13` and monitor dependency releases before widening the range. +- Harden the LLM-backed embedding adapter to consume SDK response objects directly and surface actionable errors for rate limits. +- Expand automated smoke coverage (counts endpoints, maintenance retries) to the new SQLite/Neo4j drivers to ensure regressions are caught early. +- Continue using `DUMMIES.md` to track remaining shims (FastAPI/gRPC/Celery) and log retirements as dependencies graduate into the default bootstrap path. + +## Restore and Extend Functionality +- Extend the new server-side filtering and pagination work by pushing similarity ranking into Memgraph/Neo4j so vector scoring runs without loading namespaces in Python. +- Validate consolidation heuristics at scale and tune the new exponential backoff settings (`MAINTENANCE_MAX_ATTEMPTS`, `MAINTENANCE_BASE_DELAY_SECONDS`) before enabling automated writes in production. +- Leverage the new benchmarking scripts (`scripts/evaluate_importance.py`, `scripts/consolidation_benchmark.py`, `scripts/benchmark_pagination.py`) to validate heuristics and driver performance; schedule follow-up runs against production-sized datasets. +- Introduce feedback loops for the importance heuristic (e.g., LLM-assisted ranking or analytics-driven weights) to tune thresholds over time once real-world telemetry is available. +- Build on the new `meshmind.api.grpc_server` helpers by packaging a deployable server entry point (CLI or module), wiring it + into Docker Compose, and publishing generated clients (Python + other languages) once infrastructure is available. +- Exercise the new `llm_client` overrides via REST/gRPC integration smoke tests (once credentials are available) to confirm per-request models/endpoints behave consistently outside unit tests. +- Expand predicate/registry management APIs beyond the CLI helper so services can manage vocabularies programmatically. +- Plan for reintroducing full Pydantic models once packaging support is aligned with target Python versions. + +## Improve Developer Experience +- Document usage patterns for each graph backend (memory/sqlite/memgraph/neo4j) inside `docs/` and keep the docs-guard mapping current so contributors know which pages to update when modules change. +- Add Makefile targets for running Celery workers and seeding demo data once infrastructure is provisioned (potentially reusing + the new Docker Compose stacks). +- Broaden pytest coverage with cross-backend integration tests (Memgraph/Neo4j) and failure injection to complement the new graph retrieval, CLI admin, counts smoke, benchmark, and maintenance backoff tests. +- Cache dependencies and split lint/test jobs in CI for faster feedback once the dependency stack stabilizes. +- Maintain the new `run/install_setup.sh` and `run/maintenance_setup.sh` automation scripts alongside provisioning docs so environment bootstrap stays reproducible, and document timezone-aware timestamp expectations when integrating with downstream stores. + +## Documentation & Onboarding +- Keep `README.md`, `SOT.md`, `docs/`, and onboarding guides synchronized with each release; document rerank, retrieval, and + registry flows with diagrams when possible. +- Maintain the troubleshooting section for optional tooling (ruff, pyright, typeguard, toml-sort, yamllint) now referenced in + the Makefile and expand it as new developer utilities are introduced. Keep `SETUP.md` synchronized when dependencies change. +- Provide walkthroughs for configuring LLM reranking, including sample prompts and response expectations. +- Add onboarding notes for the REST/gRPC service layers with sample payloads and curl/grpcurl snippets. + +## Future Enhancements +- Export telemetry to Prometheus/OpenTelemetry and wire alerts/dashboards around ingestion and maintenance. +- Explore streaming ingestion pipelines (queues, webhooks) for near-real-time updates. +- Investigate lightweight web UI tooling for inspecting memories, triplets, and telemetry snapshots. diff --git a/RESUME_NOTES.md b/RESUME_NOTES.md new file mode 100644 index 0000000..9aeaf11 --- /dev/null +++ b/RESUME_NOTES.md @@ -0,0 +1,43 @@ +# Resume Notes + +## Current Context + +- Branch: `integration-updates` (target PR branch: `integration`). +- Optional dependencies are bundled in the `.[dev,docs,testing]` extras, covering REST (`fastapi`, `uvicorn`), graph drivers + (`neo4j`, `pymgclient`, `redis`), Celery, LLM tooling, and developer linters. The provisioning scripts under `run/` + synchronize from `uv.lock` and verify these extras before attempting installs. +- Docker orchestration (root `docker-compose.yml` and the files in `meshmind/tests/docker/`) provisions Memgraph, Neo4j, Redis, + and optional Celery workers. Keep these references handy for live integration testing once container access is available. +- Documentation guard tooling requires that code edits touching modules with wiki pages also update the relevant files in + `docs/`. Planning artifacts (`PLAN.md`, `PROJECT.md`, `SOT.md`, `ROADMAP.md`, `PLANNING_THOUGHTS.md`) and the new `research/` + wiki are refreshed every iteration per agent instructions. + +## Latest Changes + +- Added asyncio gRPC server helpers (`meshmind.api.grpc_server`) plus pytest coverage that ingests/searches over a live channel and exercises cancellation, completing the deployable-server TODO. +- Introduced protobuf maintenance scripts (`scripts/generate_protos.py`, `scripts/check_protos.py`) alongside Makefile/CI targets (`make protos`, `make protos-check`) and packaging tests so proto drift now fails fast. +- Updated README, SETUP, `docs/api.md`, `docs/operations.md`, `docs/testing.md`, `PROJECT.md`, `PLAN.md`, `RECOMMENDATIONS.md`, `FINDINGS.md`, `ISSUES.md`, `SOT.md`, `ROADMAP.md`, `DUMMIES.md`, and `ENVIRONMENT_NEEDS.md` with the new gRPC runtime guidance and planning follow-ups; `TODO.md` now tracks follow-on work (CLI entry point, docker-compose service, docs guard updates). + +## Environment State + +- External graph services (Neo4j, Memgraph) and Redis are still unavailable inside this sandbox; integration tests depend on + fakes or SQLite until infrastructure is provisioned. +- Internet access is currently available; optional packages (`neo4j`, `pymgclient`, `fastapi`, `uvicorn`, etc.) are installed + via `uv pip`. Maintain this access so `uv.lock` can be regenerated and provisioning scripts remain effective. +- Timezone defaults across pipeline utilities now emit timezone-aware UTC timestamps; existing persisted data should be + reviewed once live backends are in play. + +## Next Session Starting Points + +1. Work through the remaining `TODO.md` priority items (Neo4j validation, backend-native vector search, live benchmarking, CLI gRPC entry point, docker-compose service). Add follow-up tasks as new discoveries surface. +2. Validate Neo4j connectivity end-to-end once a reachable instance is available, using `meshmind admin graph --backend neo4j` and the docker-compose stack. +3. Run the benchmarking scripts against production-sized or synthetic datasets hosted externally to calibrate maintenance retry defaults and pagination guidance, then document recommended values in `README.md` / `ENVIRONMENT_NEEDS.md`. +4. Implement the CLI gRPC entry point (`meshmind serve-grpc`), update docker-compose stacks to launch it, and extend docs/testing guard mappings accordingly. +5. Push vector similarity into Memgraph/Neo4j or document fallback limitations once native index strategies are available; continue updating `docs/api.md`/`docs/retrieval.md` with findings. + +## Helpful References + +- `docs/overview.md` for the module map and terminology. +- `docs/configuration.md` and `docs/operations.md` for environment variables and provisioning details. +- `SETUP.md`, `ENVIRONMENT_NEEDS.md`, and `NEEDED_FOR_TESTING.md` for onboarding and infrastructure expectations. +- `TODO.md` and `ISSUES.md` for the prioritized backlog and outstanding blockers. diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..e2fa7d9 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,27 @@ +# Roadmap + +## Vision +- Deliver a retrieval-augmented intelligence layer that ingests heterogeneous data, consolidates knowledge, and surfaces context-aware responses through REST, gRPC, and CLI workflows. +- Support multiple graph backends (in-memory, SQLite, Memgraph, Neo4j) with consistent telemetry, maintenance, and LLM orchestration knobs. +- Provide developers with reproducible tooling, comprehensive documentation, and automation scripts that keep local and CI environments aligned. + +## Near-Term (0–2 Weeks) +- Validate Neo4j and Memgraph connectivity using the new Docker Compose stacks, documenting any credential or networking gaps discovered. +- Finalize maintenance write policies by implementing retry/backoff semantics and measuring consolidation accuracy against representative datasets. +- Publish ROADMAP and PLANNING_THOUGHTS artifacts, and seed the `research/` folder with competitive analysis to ground prioritization discussions. +- Expand automated smoke tests for REST `/memories/counts`, CLI `meshmind admin counts`, and provisioning scripts to ensure guardrails stay trustworthy. +- Capture outstanding shim retirement work (Pydantic, FastAPI, FakeLLM) in CLEANUP.md with precise acceptance criteria for each removal. + +## Mid-Term (2–6 Weeks) +- Run load tests against SQLite and hosted graph backends to tune pagination defaults, consolidation heuristics, and token compression strategies. +- Implement backend-native vector similarity queries and schema indexes so embeddings never leave the database during scoring. +- Finalise the gRPC surface by building on the new asyncio server helpers—package a deployable CLI/entry point, wire it into Docker Compose, and publish generated clients (Python + additional languages) so external agents can integrate without the in-process stub. +- Instrument observability exports (Prometheus/OpenTelemetry) and wire dashboards/alerts for ingestion latency, queue depth, and error rates. +- Replace compatibility shims with official Pydantic/FastAPI packages once dependency constraints are lifted, and backfill validation coverage. + +## Long-Term (6+ Weeks) +- Build evaluation loops—analytics dashboards and LLM-assisted reviews—that continuously score memory importance heuristics and rerank quality. +- Introduce human-in-the-loop tooling for conflict resolution, allowing operators to approve merges or override automated maintenance plans. +- Explore federated deployments that synchronise multiple MeshMind instances, including replication strategies and eventual-consistency guarantees. +- Integrate additional LLM providers (Google, Anthropic, OpenRouter) through the `llm_client` abstraction, emphasizing observability and cost controls. +- Harden security posture with RBAC, audit trails, and secrets management guidelines tailored to enterprise deployments. diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..201767a --- /dev/null +++ b/SETUP.md @@ -0,0 +1,142 @@ +# Setup Guide + +This guide walks through preparing a MeshMind development machine, provisioning the +external graph/cache services, and validating that the environment is ready for local +execution and automated testing. + +## 1. Prerequisites + +- **Operating system**: Linux or macOS with Docker Engine ≥ 24 and Docker Compose v2. +- **Python**: CPython 3.11.x (the project currently supports 3.11 and 3.12). +- **System packages** (only required if you intend to install optional graph drivers): + - Build tooling: `build-essential`, `cmake`, `git`. +- Crypto/Kerberos headers: `libssl-dev`, `libkrb5-dev` (needed to compile `pymgclient`, which provides the `mgclient` module). + - Optional: `libopenblas-dev` for faster `numpy`/`scikit-learn` builds on Debian/Ubuntu. +- **Automation scripts**: `run/install_setup.sh` bootstraps a fresh environment; `run/maintenance_setup.sh` refreshes a cached + workspace. Each script checks that the optional extras (`fastapi`, `neo4j`, `pymgclient`, `uvicorn`) are declared in + `pyproject.toml`, installs `uv` if necessary, and either synchronizes from `uv.lock` or performs a validation-only dry run + when `MESH_SKIP_SYSTEM_PACKAGES=1` and/or `MESH_SKIP_PYTHON_SYNC=1` are set. The pytest suite exercises this behaviour in + `meshmind/tests/test_setup_scripts.py`. + +## 2. Bootstrap the Python environment + +1. Create and activate a virtual environment: + + ```bash + python3.11 -m venv .venv + source .venv/bin/activate + ``` + +2. Install MeshMind and all optional extras required for full local coverage: + + ```bash + python -m pip install --upgrade pip + pip install uv + uv pip install --system -e .[dev,docs,testing] # omit --system when inside an active virtualenv + ``` + + The editable install pulls in the optional dependencies used by the REST service + (`fastapi`, `uvicorn`), the graph drivers (`neo4j`, `pymgclient`, `redis`), LLM tooling + (`openai`, `tiktoken`, `sentence-transformers`), and developer utilities (ruff, + pyright, typeguard, docs tooling, pytest plugins). + +3. Copy the sample environment file and adjust values as needed: + + ```bash + cp .env.example .env + ``` + +## 3. Provision external services + +The repository ships with a root `docker-compose.yml` that starts Redis, Memgraph, and +Neo4j with sane defaults. Run the stack in the background: + +```bash +docker compose up -d +``` + +> The default Neo4j credentials are `neo4j` / `meshminD123`. Memgraph exposes Bolt on +> `bolt://localhost:7687`, the Memgraph Lab UI on `http://localhost:3000`, and an +> additional monitoring endpoint on `http://localhost:7444`. + +### 3.1 Alternate topologies for tests + +For integration scenarios or CI jobs, use the compose files in +`meshmind/tests/docker/`: + +| File | Purpose | +| --- | --- | +| `memgraph.yml` | Runs only Memgraph with local port mapping. | +| `neo4j.yml` | Runs only Neo4j with APOC enabled. | +| `redis.yml` | Runs only Redis with persistence. | +| `full-stack.yml` | Spins up all services plus an optional Celery worker. | + +Example (Memgraph only): + +```bash +docker compose -f meshmind/tests/docker/memgraph.yml up -d +``` + +### 3.2 Cleaning up + +```bash +docker compose down -v +``` + +## 4. Configure environment variables + +Update `.env` (or export variables in your shell) with the credentials for each +service: + +| Variable | Description | Default | +| --- | --- | --- | +| `MEMGRAPH_URI` | Bolt URI for Memgraph. | `bolt://localhost:7687` | +| `NEO4J_URI` | Bolt URI for Neo4j. | `bolt://localhost:7688` | +| `NEO4J_USERNAME` / `NEO4J_PASSWORD` | Neo4j auth values. | `neo4j` / `meshminD123` | +| `REDIS_URL` | Redis connection string. | `redis://localhost:6379/0` | +| `LLM_API_KEY` / `OPENAI_API_KEY` | API key for OpenAI-compatible providers (OpenAI, OpenRouter, Azure, Google). | _none_ | +| `LLM_DEFAULT_MODEL` | Default Responses model for extraction/rerank. | `gpt-5-nano` | +| `LLM_DEFAULT_BASE_URL` | Default base URL for OpenAI-compatible SDKs. | `https://api.openai.com/v1` | +| `LLM_EXTRACTION_MODEL` / `LLM_EXTRACTION_BASE_URL` | Overrides for extraction requests. | inherit defaults | +| `LLM_EMBEDDING_MODEL` / `LLM_EMBEDDING_BASE_URL` | Overrides for embedding requests. | `text-embedding-3-small` / empty | +| `LLM_RERANK_MODEL` / `LLM_RERANK_BASE_URL` | Overrides for reranking requests. | inherit defaults | +| `MAINTENANCE_MAX_ATTEMPTS` | Retry attempts for maintenance writes. | `3` | +| `MAINTENANCE_BASE_DELAY_SECONDS` | Base delay (seconds) for exponential maintenance backoff. | `1.0` | +| `SENTENCE_TRANSFORMERS_MODEL` | HuggingFace model ID used for local embeddings. | `all-MiniLM-L6-v2` | +| `DEFAULT_ENCODER` | Preferred encoder alias (`openai` or `sentence-transformers`). | `sentence-transformers` | + +Optional variables for experimentation: + +- `SQLITE_PATH` – override the path for the SQLite prototype driver. +- `GRAPH_BACKEND` – choose between `memory`, `sqlite`, `memgraph`, and `neo4j`. +- `CELERY_BROKER_URL` – override the broker used by Celery (defaults to `REDIS_URL`). +- `LLM_*` variables – adjust endpoint/model overrides per operation when targeting non-default LLM providers. +- `MAINTENANCE_MAX_ATTEMPTS` / `MAINTENANCE_BASE_DELAY_SECONDS` – tune consolidation/compression retries for your deployment. + +## 5. Smoke tests + +After installing dependencies and starting the services: + +```bash +make test +meshmind admin graph --backend memgraph +meshmind admin graph --backend neo4j +meshmind admin counts --backend sqlite +``` + +You should see passing pytest output and successful graph connectivity checks. Refer to +`docs/troubleshooting.md` if any of the services fail health checks. + +To validate the gRPC surface locally: + +```bash +python -c "from meshmind.api.grpc_server import serve_forever;\nfrom meshmind.api.memory_manager import MemoryManager;\nfrom meshmind.api.service import MemoryService;\nfrom meshmind.db.in_memory_driver import InMemoryGraphDriver;\nserve_forever(MemoryService(MemoryManager(InMemoryGraphDriver())), host='0.0.0.0', port=50051)" +``` + +In a second terminal, run: + +```bash +grpcurl -plaintext -d '{"namespace":"demo"}' localhost:50051 meshmind.api.MemoryService/MemoryCounts +``` + +Stop the server with `Ctrl+C` when finished. diff --git a/SOT.md b/SOT.md new file mode 100644 index 0000000..5d252ec --- /dev/null +++ b/SOT.md @@ -0,0 +1,170 @@ +# MeshMind Source of Truth + +This document summarizes the current architecture, supporting assets, and operational expectations for MeshMind. Update it +whenever workflows or modules change so new contributors can find accurate information in one place. + +## Repository Layout +``` +meshmind/ +├── api/ # MemoryManager CRUD adapter plus REST/gRPC service layers +├── cli/ # CLI entry point, ingest command, and admin utilities +├── client.py # High-level MeshMind façade and orchestration helpers +├── core/ # Configuration, embeddings, types, similarity, shared utilities +├── db/ # Graph driver abstractions plus in-memory, SQLite, Memgraph, and Neo4j implementations +├── models/ # Entity and predicate registries shared across the pipeline +├── pipeline/ # Extract, preprocess, compression, storage, consolidation, expiry stages with telemetry hooks +├── retrieval/ # Search strategies (hybrid, lexical, fuzzy, vector, regex, rerank helpers) +├── tasks/ # Celery beat schedules and maintenance jobs +├── testing/ # Fake drivers (Memgraph, Redis, embedding, LLM client) for offline tests +├── tests/ # Pytest suites with local fixtures (no external services required) +├── protos/ # Generated protobuf modules and schema for the gRPC surface +├── scripts/ # Operational/benchmark utilities (setup automation, evaluation, benchmarking) +└── examples/ # Scripts and notebooks showing ingestion and retrieval flows +``` +Supporting assets: +- `Makefile`: Development automation (linting, formatting, type checks, docs guard, docker compose). +- `docker-compose.yml`: Provisions Memgraph, Neo4j, and Redis for local orchestration; targeted stacks for tests live in + `meshmind/tests/docker/` (Memgraph-only, Neo4j-only, Redis-only, and full integration). +- `SETUP.md`: End-to-end provisioning instructions covering Python deps, environment variables, and Compose workflows. +- `run/install_setup.sh`, `run/maintenance_setup.sh`: Automation scripts for provisioning fresh environments and refreshing cached workspaces. +- `scripts/evaluate_importance.py`, `scripts/consolidation_benchmark.py`, `scripts/benchmark_pagination.py`: Evaluation and benchmarking tools for importance heuristics, consolidation throughput, and driver pagination performance. +- `.github/workflows/ci.yml`: GitHub Actions workflow running linting/formatting checks and pytest. +- `pyproject.toml`: Project metadata and dependency list (pins Python `>=3.11,<3.13`; see compatibility notes in `ISSUES.md`). +- Documentation (`PROJECT.md`, `PLAN.md`, `SOT.md`, `README.md`, etc.) describing the system and roadmap. +- Strategic context (`ROADMAP.md`, `PLANNING_THOUGHTS.md`, `research/overview.md`) summarising milestones, planning questions, and competitor analysis. +- `DUMMIES.md`: Catalog of temporary shims (REST/gRPC stubs, Celery dummies, fake drivers) with removal guidance and a retired + section for historical compatibility layers. + +## Configuration (`meshmind/core/config.py`) +- Loads environment variables for the active graph backend (`GRAPH_BACKEND`), Memgraph (`MEMGRAPH_URI`, `MEMGRAPH_USERNAME`, + `MEMGRAPH_PASSWORD`), SQLite (`SQLITE_PATH`), optional Neo4j (`NEO4J_URI`, `NEO4J_USERNAME`, `NEO4J_PASSWORD`), Redis + (`REDIS_URL`), maintenance retries (`MAINTENANCE_MAX_ATTEMPTS`, `MAINTENANCE_BASE_DELAY_SECONDS`), and provider-agnostic LLM + settings (`LLM_API_KEY`, `LLM_*_MODEL`, `LLM_*_BASE_URL`, legacy `OPENAI_API_KEY`). +- Uses `python-dotenv` when available to hydrate values from a `.env` file automatically. +- Provides a module-level `settings` instance used across the client, drivers, CLI, and Celery tasks. + +## Core Data Models (`meshmind/core/types.py`) +- `Memory`: Pydantic model that represents a knowledge record, including embeddings, metadata, and optional TTL. Timestamp fields now default to timezone-aware UTC values to avoid naive datetime bugs. +- `Triplet`: Subject–predicate–object edge connecting two memory UUIDs with namespace and metadata. +- `SearchConfig`: Retrieval configuration (encoder name, `top_k`, `rerank_k`, optional rerank model, metadata filters, + hybrid weights). + +## Client (`meshmind/client.py`) +- `MeshMind` bootstraps: + - Provider-agnostic LLM client constructed from `LLMConfig` (defaults derived from `LLM_*` environment variables) with support + for injectable custom clients during testing. + - Embedding model from configuration with encoder bootstrap that registers available adapters. + - Graph driver factory that creates the configured backend (memory, SQLite, Memgraph, Neo4j) lazily when persistence is required. +- Provides convenience helpers: + - Pipelines: `extract_memories`, `deduplicate`, `score_importance`, `compress`, `store_memories`, `store_triplets`. + - CRUD: `create_memory`, `update_memory`, `delete_memory`, `get_memory`, + `list_memories(namespace=None, entity_labels=None, offset=0, limit=None, query=None, use_search=None)`, + `memory_counts`, `list_triplets`. + - Retrieval: `search` (hybrid + optional LLM rerank), `search_vector`, `search_regex`, `search_exact` with automatic graph-backed loading when `memories` is omitted. +- Exposes `graph_driver`/`driver` properties that surface the active graph driver instance on demand. + +## LLM Client (`meshmind/llm_client.py`) +- `LLMConfig` normalizes environment variables and CLI overrides into per-operation settings for extraction, embeddings, and rerank workflows. +- `LLMClient` caches OpenAI-compatible client instances per base URL and exposes `responses.create` / `embeddings.create` + proxies that inject configured models/endpoints automatically. +- `build_llm_config_from_settings` and `build_default_llm_client` helpers construct instances for use across pipelines, + encoders, and the high-level client. + +## Embeddings & Utilities (`meshmind/core/embeddings.py`, `meshmind/core/utils.py`) +- `EncoderRegistry` manages encoder instances (LLM-backed embeddings, sentence-transformers, custom fixtures). +- The LLM-backed embedding adapter delegates to the shared `LLMClient`, retrying on rate limits and respecting environment/CLI overrides. +- Utility functions provide UUIDs, timestamps, hashing, and token counting guarded behind optional `tiktoken` imports. + +## Database Layer (`meshmind/db`) +- `GraphDriver` defines the persistence contract (entity/relationship upserts, querying, deletions, triplet listing) and now standardises pagination (`offset`, `limit`), server-side search (`search_entities`), and aggregated counts (`count_entities`). +- `InMemoryGraphDriver` and `SQLiteGraphDriver` power local development/testing without external services while supporting the extended contract. +- `MemgraphDriver` wraps the `mgclient` module shipped with the `pymgclient` package, handles URI parsing, executes Cypher statements, and exposes Cypher-based filtering, + pagination, and aggregation helpers alongside the Python-side vector search fallback when database-native similarity is + unavailable. +- `Neo4jGraphDriver` mirrors the Memgraph contract using the official driver (optional dependency) and now exposes server-side search and counts. +- `factory.py` exposes helpers (`create_graph_driver`, `graph_driver_factory`) to instantiate backends based on configuration. + +## Pipeline Modules (`meshmind/pipeline`) +1. **Extraction (`extract.py`)** – Orchestrates LLM Responses calls against the `Memory` schema, enforces entity label filters, and + populates embeddings via registered encoders using the configured `LLMClient`. +2. **Preprocess (`preprocess.py`)** – Deduplicates by name/embedding similarity, ensures memories have importance scores while + recording telemetry statistics, and delegates to compression when available. +3. **Compress (`compress.py`)** – Truncates metadata payloads to configurable token budgets when `tiktoken` is installed and records telemetry counters/durations. +4. **Store (`store.py`)** – Persists memories and triplets using the configured `GraphDriver`, registering predicates as needed and emitting observability events. +5. **Consolidate & Expire (`consolidate.py`, `expire.py`)** – Maintenance utilities triggered by Celery tasks to group memories, + apply batch/backoff settings, surface skipped groups, and remove stale entries. + +## Service Layers (`meshmind/api`) +- `memory_manager.py`: CRUD façade over the active graph driver that forwards namespace/entity-label filters, pagination hints, search strings, and exposes aggregate counts alongside triplet listings. +- `service.py`: Pydantic payloads and orchestration helpers shared by REST/gRPC surfaces. `MemoryService.search` leans on driver-side filtering before ranking, handles per-request LLM overrides (`use_llm_rerank`, `llm_models`, `llm_base_urls`, `llm_api_key`, `rerank_model`), and exposes `memory_counts` for CLI/HTTP usage. +- `rest.py`: `create_app` returns a FastAPI application when available or a `RestAPIStub` for tests. Routes support pagination parameters and include `/memories/counts` for namespace/label summaries. +- `grpc.py`: `GrpcServiceStub` backed by generated protobuf messages (`meshmind/protos/memory_service.proto`) to keep the Python + stub aligned with the production RPC schema. +- `grpc_server.py`: Async server helpers (`create_server`, `serve`, `serve_forever`) that expose the canonical RPC interface + over gRPC without requiring callers to duplicate lifecycle glue. + +## Retrieval (`meshmind/retrieval`) +- `filters.py`: Namespace, entity label, and metadata filtering helpers. +- `bm25.py`, `fuzzy.py`: Lexical and fuzzy scorers using scikit-learn TF-IDF + cosine and RapidFuzz WRatio, with pure-Python fallbacks when optional dependencies are unavailable. +- `vector.py`: Vector-only search utilities with cosine similarity and optional precomputed query embeddings. +- `hybrid.py`: Combines vector and BM25 scores with configurable weights defined in `SearchConfig`. +- `search.py`: Dispatchers for hybrid, BM25, fuzzy, vector, regex, and exact-match search modes plus metadata filters. +- `rerank.py`: Generic reranker interface and helper routed through the shared `LLMClient` for OpenAI-compatible providers. +- `graph.py`: Wrappers that pull candidates from the active graph driver before delegating to the existing search strategies. + +## CLI (`meshmind/cli`) +- `meshmind.cli.__main__`: Entry point exposing `ingest` and `admin` subcommands for local pipelines and maintenance tooling. +- CLI bootstraps encoder and entity registries, validates configuration early, surfaces actionable errors when optional + dependencies are missing, and routes predicate maintenance, telemetry inspection, and graph connectivity checks through + `meshmind.cli.admin`. +- Maintenance overrides (`--max-attempts`, `--base-delay`, `--run`) allow operators to tune consolidation retries per invocation before launching Celery or on-demand tasks. + +## Tasks (`meshmind/tasks`) +- `celery_app.py`: Creates the Celery application lazily, returning a shim when Celery is not installed. +- `scheduled.py`: Defines periodic consolidation, compression, and expiry jobs that initialize drivers and managers lazily, + emit observability events, persist updated memories, surface skipped consolidation groups, enforce exponential backoff for + conflicting writes (driven by `MAINTENANCE_MAX_ATTEMPTS`/`MAINTENANCE_BASE_DELAY_SECONDS`), and tolerate missing dependencies + during import. + +## API Adapter (`meshmind/api/memory_manager.py`) +- Manages CRUD operations against the graph driver, including triplet persistence and deletion helpers. +- Returns Pydantic models for list/get operations and gracefully handles missing records. + +## Models (`meshmind/models/registry.py`) +- `EntityRegistry` and `PredicateRegistry` store class metadata and permitted predicates. +- Registries are populated during bootstrap and extended as new entity/predicate types are defined. + +## Examples & Tests +- `examples/extract_preprocess_store_example.py`: Demonstrates extraction, preprocessing, triplet creation, and multiple + retrieval strategies. +- `meshmind/tests`: Pytest suites rely on fixtures (`memory_factory`, `dummy_encoder`, in-memory drivers, service stubs) and + pure-Python doubles (compatibility BaseModel, fake Memgraph/Redis/embedding/LLM clients), allowing the suite to run without + Memgraph, OpenAI, or Redis dependencies. `test_grpc_runtime.py` spins up the asyncio gRPC server helpers to validate + ingestion/search round-trips and cancellation handling, `test_protos_packaging.py` guards the packaged proto artefacts, + `test_setup_scripts.py` exercises the provisioning scripts in validation mode, `test_counts_smoke.py` covers REST/CLI count + surfaces, and `test_tasks_scheduled.py` verifies maintenance backoff semantics. + +- Required: `openai`, `pydantic`, `pydantic-settings`, `python-dotenv`. Install `pymgclient` (for the runtime `mgclient` module) when using Memgraph and install the `neo4j` + driver when targeting Neo4j. Pure-Python fallbacks exist for Pydantic, numpy, scikit-learn, and rapidfuzz but production deployments + should install the real packages. +- Optional but supported: `tiktoken`, `sentence-transformers`, `celery[redis]`, `fastapi`, `uvicorn[standard]`, `redis`, `httpx`, + `pytest-cov`. +- Development tooling introduced in the Makefile/CI expects `ruff`, `pyright`, `typeguard`, `toml-sort`, `yamllint`, `mkdocs`, and + `mkdocs-material` (now bundled in the extras). + +## Operational Notes +- Graph persistence requires a configured backend: in-memory/SQLite need no services; Memgraph requires a running instance + reachable via `settings.MEMGRAPH_URI` and the `mgclient` module provided by `pymgclient`; Neo4j requires the official driver and credentials. Use `meshmind admin graph --backend ` to sanity check connectivity or run the compose stacks. +- Encoder registration occurs during bootstrap; ensure at least one embedding encoder is available before extraction/search. +- LLM reranking flows through the shared `LLMClient`. Provide `LLM_API_KEY` (or fallback `OPENAI_API_KEY`) and confirm the + selected `SearchConfig.rerank_model` / `LLM_RERANK_MODEL` is deployed to your account or supported by the configured + `LLM_RERANK_BASE_URL`. REST/gRPC requests may override models/endpoints per call via `use_llm_rerank`, `llm_models`, + `llm_base_urls`, `llm_api_key`, and `rerank_model`. +- Provisioning scripts (`run/install_setup.sh`, `run/maintenance_setup.sh`) validate optional packages (`fastapi`, `neo4j`, + `pymgclient`, `uvicorn`) and respect `MESH_SKIP_SYSTEM_PACKAGES=1` / `MESH_SKIP_PYTHON_SYNC=1` when run in validation mode. +- Local development commands rely on external tooling (ruff, pyright, typeguard, toml-sort, yamllint); install them via the + Makefile or the CI workflow instructions in `README.md`. +- Docker Compose can be used to run Memgraph/Neo4j/Redis locally; ensure container tooling is available or provision services + manually. +- Adjust `MAINTENANCE_MAX_ATTEMPTS` and `MAINTENANCE_BASE_DELAY_SECONDS` to match production write contention levels; telemetry + (`task.maintenance.*`, `task.consolidate.failed_batches`) surfaces whether retries succeed or exhaust their budgets. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..b1679ce --- /dev/null +++ b/TODO.md @@ -0,0 +1,95 @@ +# TODO + +## Completed + +- [x] Implement dependency guards and lazy imports for optional packages (`pymgclient`, `tiktoken`, `celery`, `sentence-transformers`). +- [x] Add bootstrap helper for default encoder registration and call it from the CLI. +- [x] Update OpenAI encoder implementation to align with latest SDK responses and retry semantics. +- [x] Improve configuration guidance and automation for environment variables and service setup. +- [x] Wire `EntityRegistry` and `PredicateRegistry` into the storage pipeline and client. +- [x] Implement CRUD and triplet methods on `MeshMind`, including relationship persistence in `GraphDriver`. +- [x] Refresh examples to cover relationship-aware ingestion and retrieval flows. +- [x] Extend retrieval module with vector-only, regex, exact-match, and optional LLM rerank search helpers. +- [x] Modernize pytest suites and add fixtures to run without external services. +- [x] Expand Makefile and add CI workflows for linting, testing, and type checks. +- [x] Document or provision local Memgraph and Redis services (e.g., via docker-compose) for onboarding. +- [x] Abstract `GraphDriver` to support alternative storage backends (Neo4j, in-memory, SQLite prototype). +- [x] Add service interfaces (REST/gRPC) for ingestion and retrieval. +- [x] Introduce observability (logging, metrics) for ingestion and maintenance pipelines. +- [x] Promote the new README, archive the legacy version, and keep SOT diagrams/maps in sync. +- [x] Harden Celery maintenance tasks to initialize drivers lazily and persist consolidation results. +- [x] Replace constant importance scoring with a heuristic driven by token diversity, recency, metadata richness, and embedding magnitude. +- [x] Create fake Memgraph, Redis, and embedding drivers for testing purposes. +- [x] Expand `GraphDriver.list_entities` to support namespace/entity-label filters and propagate the behaviour through `MemoryManager`, graph retrieval wrappers, and the MeshMind client. +- [x] Extend REST/gRPC payloads, CLI helpers, and pytest coverage to exercise the new entity-label filtering semantics. +- [x] Stand up `docs/` wiki pages, `ENVIRONMENT_NEEDS.md`, and `RESUME_NOTES.md` so documentation and session hand-off stay current. +- [x] Add unit tests covering namespace/entity-label filtering for the SQLite driver and fake drivers. +- [x] Update the example pipeline (`examples/extract_preprocess_store_example.py`) to demonstrate entity-label restricted retrieval via the MeshMind client. +- [x] Document REST/gRPC request samples that include `entity_labels` in `docs/api.md`. +- [x] Add a regression test confirming `MeshMind.list_memories` forwards `entity_labels` to the memory manager. +- [x] Push graph-backed retrieval queries deeper into Memgraph/Neo4j backends so search executes without materializing entire namespaces. +- [x] Implement pagination/streaming options in `MemoryManager.list_memories` to avoid loading entire namespaces into memory. +- [x] Add CLI/admin command to report memory counts grouped by namespace and entity label for quick health checks. +- [x] Create developer tooling (pre-commit or CI check) that ensures `docs/` pages are touched when code under corresponding modules changes. +- [x] Draft a troubleshooting section documenting optional tooling installation failures (ruff, pyright, typeguard, toml-sort, yamllint) and recommended fallbacks. +- [x] Expose `memory_counts` via the gRPC stub to keep service interfaces aligned. + +- [x] Extend the docs guard mapping/tests so Docker, setup, and environment guides are enforced when related modules change. +- [x] Draft `CLEANUP.md` outlining post-restriction cleanups for files that were temporarily modified to satisfy sandbox limitations. +- [x] Audit the repository for direct `import openai` usage to scope the `llm_client` refactor. +- [x] Implement a provider-agnostic `meshmind/llm_client.py` wrapper that routes requests via configurable endpoint URLs. +- [x] Replace all direct OpenAI client interactions in the codebase with the new `llm_client` abstraction. +- [x] Update unit tests and documentation to reflect the `llm_client` usage pattern. +- [x] Extend configuration models to support per-operation LLM endpoint and model overrides with a default of `gpt-5-nano`. +- [x] Add CLI flags that override LLM endpoint/model settings when provided. +- [x] Document the cascading LLM override behaviour across README and SETUP guides. +- [x] Expose LLM override fields via REST/gRPC payloads and verify they integrate with the `llm_client` abstraction. +- [x] Add API and service-level tests covering the new LLM override payloads once implemented. +- [x] Replace `datetime.utcnow()` usage in `meshmind/_compat/pydantic.py` with timezone-aware alternatives and update any tests relying on naive timestamps. +- [x] Add a smoke test or script check that `run/install_setup.sh` and `run/maintenance_setup.sh` install key optional packages (`neo4j`, `pymgclient`, `fastapi`) when internet access is present, documenting skip behaviour when offline. +- [x] Regenerate `uv.lock` to align with the updated dependency set (`fastapi`, `uvicorn`, `neo4j`, `pymgclient`, extras) once package downloads are possible (blocked: pip cannot access PyPI from this environment). +- [x] Document per-request LLM override payloads and CLI flags across `README.md`, `docs/api.md`, and `docs/configuration.md`. +- [x] Update `SETUP.md` and `docs/operations.md` to describe the provisioning scripts' validation step and skip environment variables. +- [x] Refresh `SOT.md`, `PLAN.md`, `PROJECT.md`, and `RECOMMENDATIONS.md` to capture the LLM override workflow and timezone-aware timestamp changes. +- [x] Extend `DUMMIES.md` and `docs/testing.md` with details about `FakeLLMClient` and the new setup script smoke test. +- [x] Update `ENVIRONMENT_NEEDS.md` and `NEEDED_FOR_TESTING.md` to reflect the availability of optional packages (`fastapi`, `neo4j`, `pymgclient`, `uvicorn`). +- [x] Draft `ROADMAP.md` documenting short-, mid-, and long-term milestones extracted from the latest planning docs. +- [x] Capture architectural strategy questions in `PLANNING_THOUGHTS.md` to preserve current decision rationale. +- [x] Assemble a `research/` knowledge base summarising competitor capabilities and research references. +- [x] Implement and document maintenance retry/backoff semantics across `meshmind/tasks/scheduled.py`, configuration, and supporting docs/tests. +- [x] Add REST and CLI smoke tests covering `/memories/counts` so docs and examples stay executable with the in-memory driver. +- [x] Validate consolidation heuristics on larger datasets to confirm accuracy and stability under load. +- [x] Establish evaluation loops (analytics or LLM-assisted) to tune the new importance heuristic over time (initial synthetic benchmarking scripts in place). +- [x] Replace the compatibility shim with production Pydantic models once upstream packaging supports the target Python versions. +- [x] Verify curl/grpcurl snippets against running REST/gRPC services once infrastructure is available (FastAPI TestClient + gRPC stub coverage). +- [x] Add CLI flags for maintenance retry overrides so operators can tune `MAINTENANCE_MAX_ATTEMPTS`/`MAINTENANCE_BASE_DELAY_SECONDS` per run. +- [x] Benchmark driver-side pagination/filtering on large datasets to tune default candidate limits and document recommended overrides (synthetic benchmarks implemented). +- [x] Create a synthetic consolidation benchmark script that logs retry telemetry snapshots for analysis. +- [x] Generate protobuf definitions for the gRPC service (`meshmind/protos/memory_service.proto`) and refactor `meshmind.api.grpc` to use the canonical schema. +- [x] Update REST/gRPC documentation and tests (`README.md`, `docs/api.md`, `docs/testing.md`, `meshmind/tests/test_service_interfaces.py`, `meshmind/tests/test_api_examples.py`) to reflect the protobuf-backed interface. +- [x] Add a `make benchmarks` target that runs the synthetic benchmarking scripts and documents the workflow across README and docs. +- [x] Regenerate `uv.lock` after installing gRPC tooling and optional dependencies when network and permissions allow. +- [x] Implement asyncio gRPC server helpers (`meshmind.api.grpc_server`) and smoke tests covering ingestion/search and lifecycle cancellation. +- [x] Add packaging tests to guarantee `meshmind/protos/memory_service.proto` ships with the distribution and exposes the expected service definition. +- [x] Document runtime and operational guidance for the gRPC server across README, SETUP, `docs/api.md`, and `docs/operations.md`. +- [x] Add Makefile and CI targets (`make protos`, `make protos-check`) plus scripts to regenerate/verify protobuf bindings, failing CI when drift occurs. + +## Priority Tasks + +- [ ] Validate Neo4j driver requirements and connectivity against a live cluster (exercise CLI admin checks end-to-end). +- [ ] Implement backend-native vector similarity queries for Memgraph/Neo4j to eliminate Python-side scoring when embeddings are present. +- [ ] Run `scripts/consolidation_benchmark.py` against a ≥10k-memory dataset and document recommended retry defaults in `README.md` and `ENVIRONMENT_NEEDS.md`. +- [ ] Run `scripts/benchmark_pagination.py` against live Memgraph/Neo4j instances to tune default pagination limits and capture guidance in `docs/retrieval.md`. +- [ ] Implement integration tests exercising `meshmind admin maintenance --max-attempts/--base-delay` with a real Celery worker and Redis once infrastructure is available. +- [ ] Validate the documented curl/grpcurl snippets against deployed REST/gRPC services (with auth) once staging environments are reachable. +- [ ] Add a CLI entry point (e.g., `meshmind serve-grpc`) that constructs a `MemoryService` from settings and delegates to `meshmind.api.grpc_server.serve_forever`, with pytest coverage exercising the command in-process. +- [ ] Teach `docker-compose.yml` (and the targeted stacks) how to launch the gRPC server container once the CLI entry point exists, documenting the new service in `docs/operations.md` and `SETUP.md`. +- [ ] Add pytest coverage that executes `python scripts/check_protos.py` to ensure the verification script succeeds against the current tree. +- [ ] Extend `docs/testing.md` and the docs guard mapping so gRPC runtime changes require updates to the new CLI/server documentation once the entry point lands. + +## Recommended Waiting for Approval Tasks + +- [ ] Provision Neo4j, Memgraph, and Redis instances accessible from the development environment to unblock live integration tests (requires infrastructure approval). +- [ ] Approve installation of optional dependencies (`neo4j`, `pymgclient`, `redis`, `celery`, `tiktoken`, `sentence-transformers`) across CI and developer machines to exercise full workflows. +- [ ] Source or generate large synthetic datasets for consolidation and retrieval benchmarking to validate heuristics under load. +- [ ] Define a policy for reintroducing Pydantic models (version targets, rollout timeline) so compatibility shims can be retired once approved. diff --git a/docker-compose.yml b/docker-compose.yml index 96be0b4..cdf9fca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,73 @@ -version: "3.8" +version: "3.9" + services: memgraph: image: memgraph/memgraph-platform:latest + container_name: meshmind-memgraph + restart: unless-stopped ports: - "7687:7687" + - "7444:7444" - "3000:3000" + healthcheck: + test: ["CMD", "bash", "-c", "cypher-shell --version || exit 1"] + interval: 15s + timeout: 10s + retries: 5 + environment: + MEMGRAPH_MEMORY_LIMIT: 2GB + volumes: + - memgraph-data:/var/lib/memgraph + networks: + - meshmind + + neo4j: + image: neo4j:5.26 + container_name: meshmind-neo4j + restart: unless-stopped + ports: + - "7688:7687" + - "7474:7474" + environment: + NEO4J_AUTH: neo4j/meshminD123 + NEO4J_PLUGINS: '["apoc"]' + NEO4J_apoc_export_file_enabled: "true" + NEO4J_apoc_import_file_enabled: "true" + NEO4J_dbms_security_procedures_unrestricted: apoc.* + healthcheck: + test: ["CMD", "cypher-shell", "-u", "neo4j", "-p", "meshminD123", "RETURN 1"] + interval: 15s + timeout: 10s + retries: 5 + volumes: + - neo4j-data:/data + - neo4j-logs:/logs + networks: + - meshmind + redis: image: redis:7-alpine - worker: - build: . - command: celery -A meshmind.tasks.celery_app worker -B -l info - depends_on: - - memgraph - - redis \ No newline at end of file + container_name: meshmind-redis + restart: unless-stopped + command: redis-server --save 60 1000 --loglevel warning + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + volumes: + - redis-data:/data + networks: + - meshmind + +networks: + meshmind: + name: meshmind + +volumes: + memgraph-data: + neo4j-data: + neo4j-logs: + redis-data: diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..e2e6485 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,102 @@ +# Service Interfaces & CLI + +MeshMind exposes multiple integration points for ingestion and retrieval workflows. + +## Memory Service (`meshmind.api.service`) + +- `MemoryService` encapsulates ingestion (`ingest_memories`, `ingest_triplets`), search, and CRUD operations. +- `SearchPayload` accepts `entity_labels`, allowing clients to restrict search to specific entity types before hybrid + ranking occurs. It also exposes `use_llm_rerank`, `llm_models`, `llm_base_urls`, `llm_api_key`, and `rerank_model` so API + callers can override the shared `LLMClient` on a per-request basis. When these dictionaries are omitted, the service falls + back to `LLM_*` environment variables and then the built-in defaults (`gpt-5-nano`, `text-embedding-3-small`). +- `MemoryPayload` and `TripletPayload` are Pydantic-compatible models shared by REST and gRPC stubs. + +## REST API (`meshmind.api.rest`) + +- `create_app` dynamically returns a FastAPI application if FastAPI is installed, otherwise a lightweight stub. +- Routes: + - `POST /memories`: ingest a batch of memories. + - `POST /triplets`: ingest relationships. + - `POST /search`: execute hybrid search. + - `GET /memories`: list memories (accepts `namespace`, `entity_labels`, `offset`, `limit`, `query`, `use_search`). + - `GET /triplets`: list triplets. + - `GET /memories/counts`: summarize memory counts grouped by namespace and entity label. +- Sample request bodies: + ```json + { + "query": "python", + "namespace": "demo", + "entity_labels": ["Memory"], + "top_k": 5, + "encoder": "text-embedding-3-small" + } + ``` + ```json + { + "query": "architecture", + "namespace": "demo", + "top_k": 5, + "use_llm_rerank": true, + "llm_models": {"rerank": "openrouter/reranker-v1"}, + "llm_base_urls": {"rerank": "https://openrouter.ai/api/v1"} + } + ``` + ```json + { + "namespace": "demo", + "entity_labels": ["Memory"], + "limit": 20, + "query": "python" + } + ``` + ```json + { + "namespace": "demo" + } + ``` +- The `RestAPIStub` mirrors these routes for tests without requiring FastAPI. +- Example `curl` invocations against a local FastAPI server: + ```bash + curl -s -X POST http://localhost:8000/search \ + -H "Content-Type: application/json" \ + -d '{"query":"architecture","namespace":"demo","entity_labels":["Memory"],"top_k":5}' + curl -s "http://localhost:8000/memories/counts?namespace=demo" + ``` + +## gRPC Service (`meshmind.api.grpc` + `meshmind/api/grpc_server.py`) + +- `meshmind/protos/memory_service.proto` defines the canonical MeshMind RPC schema. Use `python scripts/generate_protos.py` to + regenerate `memory_service_pb2.py` and `memory_service_pb2_grpc.py`; CI runs `scripts/check_protos.py` to fail when the + generated modules drift. +- `GrpcServiceStub` wires the generated protobuf messages to the `MemoryService` business logic so unit tests and demos can run + without standing up a gRPC server. +- For production deployments, construct a `MemoryService` and call + `meshmind.api.grpc_server.serve_forever(memory_service, host, port)` to expose the RPC interface. +- `SearchPayload` mirrors the REST payload including `entity_labels`, `use_llm_rerank`, per-operation LLM overrides, and + rerank-specific settings. Overrides remain scoped to the single RPC and never mutate the global client configuration. +- `MemoryCountsRequest` returns namespace/entity-label aggregates so service parity with REST/CLI is preserved. +- A running gRPC server can be exercised with `grpcurl`: + ```bash + grpcurl -plaintext -d '{"namespace":"demo"}' localhost:50051 meshmind.api.MemoryService/MemoryCounts + ``` + +## CLI (`meshmind/cli`) + +- `meshmind.cli.__main__` bootstraps default encoders, validates configuration, and exposes ingestion commands. +- `meshmind.cli.admin` contains administrative tasks for registry inspection, backend connectivity checks, memory counts, + and maintenance triggers. Use `meshmind admin maintenance --max-attempts --base-delay --run ` to + override retry/backoff settings per run. + +## Client (`meshmind/client.py`) + +- `MeshMind` client wraps all pipelines and persistence methods. +- CRUD and search helpers delegate to the memory manager while respecting namespace/entity label filters. +- Search helpers automatically fetch candidates from the graph when `memories` is omitted, ensuring consistent + behaviour across service surfaces. + +## Example Workflow + +1. Instantiate the client: `mm = MeshMind()` (ensure environment variables are set for your backend). +2. Ingest data using CLI (`meshmind ingest ...`) or the REST API. +3. Retrieve memories via REST (`POST /search`) or the Python client (`mm.search(...)`). +4. Monitor metrics/logs through the observability utilities (`docs/telemetry.md`). diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..794d7c9 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,62 @@ +# Architecture + +MeshMind is organized around a layered architecture that separates extraction, persistence, and retrieval concerns while +keeping the graph storage pluggable. + +## High-Level Flow + +1. **Extraction** (`meshmind.pipeline.extract`) + - Uses instructions plus LLM client and embeddings to convert raw content into `Memory` objects and `Triplet` + relationships. +2. **Preprocessing** (`meshmind.pipeline.preprocess`) + - Deduplicates, scores importance, compresses content, and prepares payloads for persistence. +3. **Storage** (`meshmind.pipeline.store`) + - Persists memories and triplets through the active graph driver, registering entity/predicate schemas along the way. +4. **Retrieval** (`meshmind.retrieval`) + - Provides hybrid search, vector-only, regex, exact, fuzzy, BM25, and optional LLM rerank flows across stored + memories. +5. **Maintenance** (`meshmind.pipeline.consolidate`, `meshmind.tasks.scheduled`) + - Consolidates duplicates, expires stale items, and runs periodic graph hygiene tasks. + +## Key Components + +- **MeshMind Client (`meshmind/client.py`)** + - High-level façade that wires pipelines, storage, and retrieval helpers. Lazily loads a graph driver via the factory + configured by `meshmind.core.config.settings`. + - Provides CRUD utilities, search helpers, and bootstrap logic for registries and encoders. +- **Memory Service Layer (`meshmind/api/service.py`)** + - Encapsulates ingestion/search/triplet operations, enabling REST/gRPC adapters and CLI commands to share behaviour. + - Applies configuration (e.g., `SearchConfig`) and now filters `list_memories` calls by namespace and entity labels to + minimize driver loads. +- **Graph Drivers (`meshmind/db/*`)** + - `InMemoryGraphDriver`: simple dictionaries for tests and demos. + - `SQLiteGraphDriver`: lightweight relational persistence using JSON columns. + - `Neo4jGraphDriver` and `MemgraphDriver`: Bolt-based implementations using Cypher. + - All drivers implement the expanded `GraphDriver.list_entities(namespace, entity_labels)` contract for efficient + filtering at the storage layer. +- **Observability (`meshmind/core/observability.py`)** + - Tracks metrics, counters, and structured logs for pipeline operations. + +## Data Model + +Memories capture: + +- `uuid`: unique identifier. +- `namespace`: logical partition to isolate tenants/workloads. +- `entity_label`: semantic label for type filtering. +- `embedding`: vector representation for similarity search. +- `metadata`: arbitrary JSON payload with content, source, annotations. +- `importance`, `ttl_seconds`, `reference_time`: scheduling and scoring hints. + +Triplets connect memories with: + +- `subject`, `predicate`, `object`: relationship endpoints. +- `entity_label`: stored predicate label to support filtering. +- `metadata`: additional context for the relationship. + +## Extensibility + +- Register new entity or predicate schemas via `EntityRegistry` and `PredicateRegistry` before persistence. +- Provide alternate graph drivers by subclassing `GraphDriver` and implementing the CRUD/search primitives. +- Extend retrieval strategies by adding new modules under `meshmind/retrieval` and wiring them through the client and + service layers. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..21e6ca5 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,54 @@ +# Configuration Reference + +MeshMind configuration is derived from environment variables and optional `.env` files (loaded when `python-dotenv` is +installed). + +## Core Settings + +| Variable | Description | Default | +| -------- | ----------- | ------- | +| `GRAPH_BACKEND` | Storage backend (`memory`, `sqlite`, `neo4j`, `memgraph`). | `memory` | +| `MEMGRAPH_URI` | Bolt URI for Memgraph. | `bolt://localhost:7687` | +| `MEMGRAPH_USERNAME` / `MEMGRAPH_PASSWORD` | Credentials for Memgraph. | empty | +| `NEO4J_URI` | Bolt URI for Neo4j. | `bolt://localhost:7688` | +| `NEO4J_USERNAME` / `NEO4J_PASSWORD` | Credentials for Neo4j. | `neo4j` / `meshminD123` | +| `SQLITE_PATH` | File path for SQLite backend. | `:memory:` | +| `REDIS_URL` | Redis connection string for caching/background tasks. | `redis://localhost:6379/0` | +| `MAINTENANCE_MAX_ATTEMPTS` | Number of retries for maintenance write conflicts. | `3` | +| `MAINTENANCE_BASE_DELAY_SECONDS` | Base backoff delay (exponential) between maintenance retries. | `1.0` | +| `OPENAI_API_KEY` | Legacy API key fallback used when `LLM_API_KEY` is unset. | empty | +| `LLM_API_KEY` | Preferred API key for any OpenAI-compatible provider. | inherits `OPENAI_API_KEY` | +| `LLM_DEFAULT_MODEL` | Fallback Responses model for extraction/reranking. | `gpt-5-nano` | +| `LLM_DEFAULT_BASE_URL` | Base URL for providers using the OpenAI SDK. | empty | +| `LLM_EXTRACTION_MODEL` / `LLM_EXTRACTION_BASE_URL` | Overrides for extraction runs. | inherit default model/base | +| `LLM_EMBEDDING_MODEL` / `LLM_EMBEDDING_BASE_URL` | Overrides for embedding requests. | `text-embedding-3-small` / empty | +| `LLM_RERANK_MODEL` / `LLM_RERANK_BASE_URL` | Overrides for reranking requests. | inherit default model/base | +| `EMBEDDING_MODEL` | Backwards-compatible alias for `LLM_EMBEDDING_MODEL`. | `text-embedding-3-small` | + +## Derived Behaviour + +- `Settings.missing()` reports missing variables based on the selected backend (e.g., Memgraph credentials ignored when + not using Memgraph). +- CLI commands (`meshmind.cli.__main__`) surface missing configuration early so ingestion fails fast. +- The MeshMind client reads settings during initialization to configure the default `LLMClient` and graph driver factory. +- CLI arguments (`--llm-api-key`, `--llm-base-url`, `--extraction-model`, etc.) override environment values for a single run, + and API payloads may pass explicit models/endpoints where supported. The precedence order is per-request override (CLI flag + or payload) > environment variables > built-in defaults described above. +- REST/gRPC search requests accept `use_llm_rerank`, `llm_models`, `llm_base_urls`, `llm_api_key`, and `rerank_model` so callers + can experiment with alternative providers without mutating global configuration. +- Consolidation maintenance relies on `MAINTENANCE_MAX_ATTEMPTS` and + `MAINTENANCE_BASE_DELAY_SECONDS` to apply exponential backoff when writes + collide; increase the delay or attempt count when running against + multi-writer clusters. + +## Recommended Secrets Management + +- Store secrets in a `.env` file for local development (loaded automatically when `python-dotenv` is present). +- Use environment-specific secret managers (Vault, AWS Secrets Manager, etc.) for production deployments. +- Rotate API keys/credentials regularly and update the environment variables accordingly. + +## Extending Configuration + +- Add new settings to `meshmind/core/config.py` with sensible defaults. +- Update `Settings.REQUIRED_GROUPS` when additional capabilities require environment validation. +- Document the new variables here, in `README.md`, and in `ENVIRONMENT_NEEDS.md` if they require provisioning support. diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..7b325af --- /dev/null +++ b/docs/development.md @@ -0,0 +1,45 @@ +# Development Workflow + +This guide summarizes expectations when contributing to MeshMind. + +## Prerequisites + +- Python 3.11 or 3.12 is recommended (see `pyproject.toml`). +- Install dependencies with `pip install -e .[dev,docs,testing]` (or `uv pip install --system -e .[dev,docs,testing]`; drop + `--system` when using an activated virtualenv). +- Optional extras: none—`.[dev,docs,testing]` pulls in REST tooling (`fastapi`, `uvicorn`), graph drivers (`neo4j`, `pymgclient`, + `redis`), LLM tooling (`openai`, `tiktoken`, `sentence-transformers`), and developer utilities. Refer to `SETUP.md` for + service provisioning steps. + +## Coding Standards + +- Follow the style rules documented in `AGENTS.md` (120-character lines, meaningful headings, bullet lists for + enumerations). +- Keep code modular and dependency-light to maintain deterministic tests. +- Avoid wrapping imports in `try/except` unless explicitly handling optional dependencies. + +## Documentation + +- Update `README.md`, `CHANGELOG.md`, `docs/`, `SOT.md`, and other root markdown files whenever behaviour changes. +- Each change batch must append a timestamped entry to `CHANGELOG.md` describing modules, functions, and rationale. +- Maintain `RESUME_NOTES.md` at the end of every turn to capture context for future sessions. +- Run `make docs-guard` (optionally with `BASE_REF=`) before pushing to ensure code changes have matching documentation updates. + +## Testing + +- Run `pytest` before committing. +- Add unit tests for new features, especially when touching drivers, pipelines, or retrieval logic. +- Use the fake drivers and fixtures to avoid requiring external services. + +## Git Workflow + +- Work on feature branches derived from the current branch (e.g., `work`). +- Commit logically grouped changes with descriptive messages. +- Generate PR summaries via the automated tooling once commits are ready. + +## Continuous Integration + +- `.github/workflows/ci.yml` runs linting and tests; ensure new code paths are covered. +- The Makefile includes shortcuts (`make test`, `make lint`, `make format`, `make protos`, `make protos-check`) for local validation. +- CI also executes `make docs-guard` and `make protos-check` so missing documentation updates or out-of-date protobuf bindings + will fail the pipeline. diff --git a/docs/operations.md b/docs/operations.md new file mode 100644 index 0000000..ae95762 --- /dev/null +++ b/docs/operations.md @@ -0,0 +1,78 @@ +# Operations & Maintenance + +This guide covers operational tasks for MeshMind deployments. + +## Configuration + +- Environment variables are read via `meshmind.core.config.Settings`. +- Critical variables: + - `GRAPH_BACKEND`: `memory`, `sqlite`, `neo4j`, or `memgraph`. + - Connection URIs/usernames/passwords for Neo4j/Memgraph. + - `SQLITE_PATH` when using the SQLite backend. + - `REDIS_URL`, `LLM_API_KEY`/`OPENAI_API_KEY`, and the `LLM_*` model/base URL overrides for optional LLM integrations. +- `settings.missing()` groups missing variables by capability to simplify diagnostics. + +## Provisioning Scripts + +- `run/install_setup.sh` installs system packages, ensures `uv` is available, validates that optional dependencies + (`fastapi`, `neo4j`, `pymgclient`, `uvicorn`) are declared in `pyproject.toml`, and syncs Python requirements from + `uv.lock` when present. Use `MESH_SKIP_SYSTEM_PACKAGES=1` and/or `MESH_SKIP_PYTHON_SYNC=1` to perform a validation-only run + when network access is unavailable. +- `run/maintenance_setup.sh` refreshes cached environments with the same validation logic and respects the same skip flags so + CI can dry-run provisioning. + +## gRPC Service Deployment + +- Generate or verify protobuf bindings with `python scripts/generate_protos.py` and `python scripts/check_protos.py`. CI runs + the check script automatically so outdated bindings fail fast. +- Construct a `MemoryService` (for example, using the driver factory) and call + `meshmind.api.grpc_server.serve_forever(service, host="0.0.0.0", port=50051)` to expose the RPC interface. +- To embed the server inside a larger application, call `meshmind.api.grpc_server.create_server(...)`, start the returned + `grpc.aio.Server`, and integrate its lifecycle with your event loop. +- `grpcurl -plaintext -d '{"namespace":"demo"}' localhost:50051 meshmind.api.MemoryService/MemoryCounts` queries the + `/memories/counts` RPC for smoke testing. + +## Bootstrap & Registries + +- `meshmind.core.bootstrap.bootstrap_entities` ensures the `Memory` model is registered. +- `meshmind.core.bootstrap.bootstrap_encoders` primes default encoders via the provider-agnostic `LLMClient` when available. +- Registry state (entities/predicates) persists in process memory; configure the CLI/admin tasks to inspect/refresh when + adding new schema types. + +## Scheduled Tasks + +- `meshmind.tasks.scheduled.expire_task`: prunes expired memories based on TTL metadata. +- `meshmind.tasks.scheduled.consolidate_task`: merges duplicates, persists consolidation results, emits telemetry, and retries + conflicting writes using exponential backoff (`MAINTENANCE_MAX_ATTEMPTS`, `MAINTENANCE_BASE_DELAY_SECONDS`). Conflicts and + retry durations are logged for later analysis. +- `meshmind.tasks.scheduled.compress_task`: re-compresses long memories and persists updates to the configured backend. +- All tasks rely on `MemoryManager.list_memories` and respect namespace/label filters for efficiency. + +## Observability + +- `meshmind.core.observability.telemetry` exposes counters and gauges with `snapshot()` for inspection. +- The `docs/telemetry.md` page lists the available metrics and logging patterns. + +## Admin CLI + +- `meshmind.cli.admin` provides commands to: + - Validate backend connectivity (Neo4j/Memgraph/SQLite). + - Summarize stored memories via `mesh admin counts --namespace ` grouped by entity label. + - Run consolidation plans manually. + - Tune maintenance retries per invocation with `meshmind admin maintenance --max-attempts --base-delay --run `. + - Inspect registry contents. + - Summarize configuration with sensitive values masked. + Automated smoke tests exercise the `/memories/counts` REST endpoint and the CLI counts command using the in-memory driver so + documentation stays in sync with working behaviour. + +## Benchmarking Utilities + +- `make benchmarks` runs the synthetic benchmarking scripts (`scripts/evaluate_importance.py`, `scripts/consolidation_benchmark.py`, `scripts/benchmark_pagination.py`) with fast defaults and stores JSON summaries in `build/benchmarks/`. +- Override script flags to stress specific backends (for example `--backend neo4j` or higher iteration counts) once live services are provisioned, and capture findings in `FINDINGS.md` / `ENVIRONMENT_NEEDS.md`. + +## Deployment Considerations + +- Provision graph databases externally (Docker, managed service) and expose Bolt endpoints reachable from the runtime. +- Ensure optional dependencies (`neo4j`, `pymgclient`, `redis`, `celery`, `fastapi`, `uvicorn`, `tiktoken`) are installed where required (or install `.[dev,docs,testing]`). +- Configure logging/metrics sinks to capture telemetry emitted by pipeline stages. +- Use `docker-compose.yml` as a reference for local orchestration (Memgraph, Neo4j, Redis) and the targeted stacks in `meshmind/tests/docker/` for integration testing. diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 0000000..560311b --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,34 @@ +# MeshMind Overview + +MeshMind is a modular framework for extracting, storing, and retrieving "memories"—structured knowledge units that +combine embeddings, metadata, and graph relationships. The platform orchestrates LLM-powered extraction pipelines, +pluggable persistence backends, and multiple retrieval strategies so applications can ingest heterogeneous content and +query it with hybrid search techniques. + +## Core Concepts + +- **Memories** (`meshmind.core.types.Memory`): normalized documents with embeddings, importance scores, TTLs, and + metadata. +- **Triplets** (`meshmind.core.types.Triplet`): relationship edges connecting memories by subject/predicate/object. +- **Graph Drivers** (`meshmind.db`): persistence adapters for in-memory usage, SQLite prototyping, and Bolt-compatible + services (Neo4j, Memgraph). +- **Pipelines** (`meshmind.pipeline`): modular stages for extraction, preprocessing, compression, and storage. +- **Retrieval** (`meshmind.retrieval`): text, vector, graph, and rerank utilities that operate on in-memory or driver- + backed memories. +- **Service Interfaces** (`meshmind.api`): REST/gRPC-compatible surfaces and CLI tooling to automate ingestion and + maintenance workflows. + +## Project Layout + +- `meshmind/core`: fundamental types, configuration, bootstrap helpers, encoders, similarity metrics, observability, + and shared utilities. +- `meshmind/pipeline`: ingestion lifecycle components (`extract`, `preprocess`, `store`, `compress`, `consolidate`, + `expire`). +- `meshmind/db`: graph drivers plus the factory that selects the active backend based on environment configuration. +- `meshmind/retrieval`: hybrid search primitives, graph-aware wrappers, and optional LLM rerank integration. +- `meshmind/api`: Memory service layer, REST/gRPC adapters, CLI entry points, and admin tooling. +- `meshmind/tests`: unit and integration-style tests that exercise pipelines, drivers, API stubs, and search flows + using in-memory or fake dependencies. +- `examples/`: runnable demos showing end-to-end extraction, preprocessing, and storage pipelines. + +Consult the other `docs/*.md` pages for deep dives into each subsystem. diff --git a/docs/persistence.md b/docs/persistence.md new file mode 100644 index 0000000..e4a6fa4 --- /dev/null +++ b/docs/persistence.md @@ -0,0 +1,62 @@ +# Persistence Layer + +MeshMind persists memories and triplets through interchangeable graph drivers. Each driver implements the +`meshmind.db.base_driver.GraphDriver` interface, which now accepts both namespace and entity-label filters for +`list_entities`. + +## Driver Capabilities + +| Driver | Module | Use Case | Notes | +| ------ | ------ | -------- | ----- | +| In-memory | `meshmind.db.in_memory_driver.InMemoryGraphDriver` | Tests and demos | Stores nodes/edges in Python dicts with UUID auto-generation helpers, plus in-process search/pagination implementations. | +| SQLite | `meshmind.db.sqlite_driver.SQLiteGraphDriver` | Lightweight persistence without external services | Uses two tables (`entities`, `triplets`) with JSON columns; supports namespace + label filtering, pagination, and SQL-based search. | +| Neo4j | `meshmind.db.neo4j_driver.Neo4jGraphDriver` | Production Bolt cluster | Requires the `neo4j` Python driver and connectivity to a running instance. Provides `verify_connectivity()` for health checks, server-side search, and aggregated counts. | +| Memgraph | `meshmind.db.memgraph_driver.MemgraphDriver` | Memgraph (Bolt-compatible) | Requires the `pymgclient` package (provides the `mgclient` module). Filters results using Cypher with optional namespace/label predicates, driver-side search, and count aggregation. | +| Fake drivers | `meshmind.testing.fakes` | Offline testing | Provide stubbed implementations for Redis, embedding models, and Memgraph. | + +## Driver Factory + +`meshmind.db.factory.graph_driver_factory` inspects `meshmind.core.config.settings.GRAPH_BACKEND` and constructs the +corresponding driver. Supported values: + +- `memory` +- `sqlite` +- `neo4j` +- `memgraph` + +Environment variables (see `README.md` and `ENVIRONMENT_NEEDS.md`) provide connection URIs and credentials. + +## MemoryManager + +`meshmind.api.memory_manager.MemoryManager` wraps driver operations and now exposes: + +```python +list_memories( + namespace: str | None = None, + entity_labels: Sequence[str] | None = None, + *, + offset: int = 0, + limit: int | None = None, + query: str | None = None, + use_search: bool | None = None, +) -> List[Memory] + +count_memories(namespace: str | None = None) -> Dict[str, Dict[str, int]] +``` + +Filtering and pagination at this layer keep retrieval queries efficient when large graphs are present. The manager defers +to driver-provided `search_entities` and `count_entities` helpers when available, then handles `add_memory`, +`update_memory`, `delete_memory`, and triplet equivalents by normalizing payloads and delegating to the driver. + +## Maintenance & Consolidation + +Scheduled tasks in `meshmind.tasks.scheduled` use the memory manager to fetch entities before running consolidation or +expiration strategies. Because `list_memories` accepts entity labels, maintenance jobs can focus on specific entity +classes without scanning the full graph. + +## Adding a New Driver + +1. Subclass `GraphDriver` and implement the abstract methods, including the namespace/label-aware `list_entities`. +2. Register the driver in `meshmind/db/factory.py` under a new backend key. +3. Provide fake/test doubles if the backend cannot run inside CI. +4. Update documentation (`docs/persistence.md`, `SOT.md`, `README.md`) and add tests validating CRUD behaviours. diff --git a/docs/pipelines.md b/docs/pipelines.md new file mode 100644 index 0000000..37a90ac --- /dev/null +++ b/docs/pipelines.md @@ -0,0 +1,47 @@ +# Pipelines + +MeshMind pipelines orchestrate the transformation of raw content into stored memories and relationships. + +## Extract (`meshmind.pipeline.extract`) + +- `extract_memories` uses the configured LLM client to parse unstructured content into structured `Memory` objects. +- Supports configurable entity types, instructions, and embedding models. +- Defaults are sourced from `LLM_*` environment variables; pass a custom `LLMConfig` or CLI overrides (`meshmind ingest`) to + target different providers or models for extraction and embeddings. +- Output includes candidate triplets emitted by extraction heuristics. + +## Preprocess (`meshmind.pipeline.preprocess`) + +- `deduplicate`: removes near-duplicate memories based on embedding cosine similarity and metadata hashes. +- `score_importance`: applies heuristic scoring that considers recency, metadata richness, token diversity, and embedding + magnitude. +- `compress`: performs optional content compression if tiktoken is installed; otherwise acts as a no-op while recording + telemetry. + +## Store (`meshmind.pipeline.store`) + +- `store_memories`: upserts each `Memory` through the graph driver after ensuring the entity type is registered. +- `store_triplets`: persists relationships while registering predicates via `PredicateRegistry`. + +## Consolidate (`meshmind.pipeline.consolidate`) + +- `consolidate_memories`: groups similar memories, merges metadata, and produces a plan enumerating consolidation + outcomes for later application. + +## Maintenance (`meshmind.tasks.scheduled`) + +- Provides task stubs for Celery/cron that run consolidation plans, expire TTL-bound memories, and emit importance + telemetry snapshots. + +## Expiration (`meshmind.pipeline.expire`) + +- Contains helpers to remove expired memories (leveraged by maintenance tasks and CLI commands). + +## Usage Patterns + +1. Use the MeshMind client (`MeshMind.extract_memories`) or pipeline functions directly to generate memories, supplying a custom + `LLMConfig` when provider-specific overrides are required. +2. Preprocess the output with `deduplicate`, `score_importance`, and `compress` depending on your quality requirements. +3. Persist via `store_memories` / `store_triplets` using your configured graph backend. +4. Retrieve with hybrid or specialized searches (see `docs/retrieval.md`). +5. Schedule maintenance tasks to keep the graph clean and heuristics calibrated. diff --git a/docs/retrieval.md b/docs/retrieval.md new file mode 100644 index 0000000..ecfed61 --- /dev/null +++ b/docs/retrieval.md @@ -0,0 +1,59 @@ +# Retrieval Strategies + +MeshMind ships with multiple retrieval strategies that operate on `Memory` collections. Each strategy can run purely in +memory (provided via argument) or load candidates directly from the configured graph driver. + +## Hybrid Search + +`meshmind.retrieval.search.search` + +- Combines BM25 (text), vector similarity, regex, exact-match, and fuzzy signals. +- Accepts `SearchConfig` to tune weights, top-k counts, embedding encoder, and rerank parameters. +- Optional rerank step calls `meshmind.retrieval.llm_rerank.llm_rerank` using the active LLM client. Per-operation models and + endpoints cascade from `LLM_*` environment variables but can be overridden via `SearchConfig` or CLI flags. + +Graph wrapper: `graph_hybrid_search(query, driver, namespace=None, entity_labels=None, config=None, reranker=None)` +leans on `MemoryManager.list_memories(..., query=query, use_search=True)` so Memgraph/Neo4j filter and paginate on the +server. The helper automatically expands the candidate window (`top_k * 5`, `rerank_k * 2`) to balance recall with +efficiency. + +## Vector Search + +`meshmind.retrieval.vector.search_vector` + +- Uses cosine similarity between query embedding and stored embeddings. +- Graph wrapper `graph_vector_search` now requests driver-side filtering (`query=`) and pagination, reducing + the number of memories materialised before vector scoring. + +## Textual Search + +- **Regex** (`search_regex` / `graph_regex_search`): applies compiled regular expressions against string fields. +- **Exact** (`search_exact` / `graph_exact_search`): equality comparisons with optional case sensitivity, field + selection, and top-k truncation. +- **Fuzzy** (`search_fuzzy` / `graph_fuzzy_search`): Levenshtein distance using `rapidfuzz` if installed. +- **BM25** (`search_bm25` / `graph_bm25_search`): rank text fields using BM25 weighting. + +## Entity Label Filtering & Pagination + +All graph-backed helpers accept `entity_labels`. The labels and pagination hints (`offset`, `limit`) are forwarded to +`MemoryManager` and ultimately to the graph driver so only relevant entity types are hydrated into Python. This prevents +unnecessary deserialization when a graph contains many heterogeneous memory types and unlocks efficient infinite-scroll or +batch processing patterns. + +## Search Configuration + +`SearchConfig` (`meshmind.core.types.SearchConfig`) controls: + +- `top_k`: maximum results to return. +- `encoder`: embedding model name (default comes from settings and the MeshMind client). +- `rerank_k`: number of documents to rerank with LLM. +- `rerank_model` / `rerank_endpoint`: explicit overrides that take precedence over environment defaults when reranking. +- `fields`: optional mapping for textual searches (regex, exact, fuzzy) to target metadata keys. + +## Extending Retrieval + +1. Add a new module under `meshmind/retrieval` with a function that accepts `(query, memories, **kwargs)`. +2. Update `meshmind/retrieval/__init__.py` and the MeshMind client to expose the helper. +3. Create graph wrappers if the strategy benefits from driver-backed loading. +4. Add unit tests in `meshmind/tests/test_retrieval.py` or a dedicated module to ensure determinism. +5. Document the feature here and in `README.md`. diff --git a/docs/telemetry.md b/docs/telemetry.md new file mode 100644 index 0000000..e62cb33 --- /dev/null +++ b/docs/telemetry.md @@ -0,0 +1,33 @@ +# Telemetry & Observability + +MeshMind provides lightweight observability utilities under `meshmind.core.observability` to trace pipeline behaviour. + +## Telemetry API + +- `telemetry.reset()`: clear counters/gauges (used in tests). +- `telemetry.increment(name, value=1.0, tags=None)`: bump a counter. +- `telemetry.gauge(name, value, tags=None)`: record the latest gauge value. +- `telemetry.timer(name, tags=None)`: context manager to measure elapsed time. +- `telemetry.snapshot()`: return a dictionary of counters/gauges/timers for inspection. + +## Instrumented Components + +- `meshmind.pipeline.preprocess.score_importance`: records importance distribution metrics. +- `meshmind.pipeline.compress.compress`: wraps compression attempts to measure latency and failure rates. +- `meshmind.pipeline.store` and `meshmind.tasks.scheduled`: emit counters for stored items and maintenance plans. Consolidation + retries now record `task.maintenance.retries`, `task.maintenance.retry_success`, and `task.maintenance.failures`, while + `task.consolidate.failed_batches` gauges highlight batches that exhausted their retry budget. +- Observability hooks are intentionally dependency-free to keep tests deterministic. + +## Integrations + +- For production use, wrap the telemetry API with adapters that forward metrics to Prometheus, StatsD, or logging + systems. +- Configure logging handlers (see `logging` usage throughout the pipeline modules) to integrate with your preferred log + aggregation stack. + +## Best Practices + +- Tag metrics with `namespace` when running multi-tenant workloads. +- Reset telemetry within tests to avoid cross-test contamination. +- Extend the telemetry module with thread-safe transports if moving beyond single-process deployments. diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..d19a486 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,72 @@ +# Testing & Quality + +MeshMind includes an extensive pytest suite that runs without external services by relying on fake drivers and in-memory +backends. + +## Test Topology + +- `meshmind/tests/test_pipeline_*`: validate extraction, preprocessing, storage, and maintenance flows with dummy + drivers. +- `meshmind/tests/test_db_*`: ensure graph drivers implement CRUD semantics (with fakes for optional dependencies). +- `meshmind/tests/test_retrieval.py` and `test_graph_retrieval.py`: exercise hybrid, textual, vector, and graph-based + searches. +- `meshmind/tests/test_service_interfaces.py`: cover REST and gRPC stubs, including entity-label filtering, pagination hints, and memory count routes with LLM override payloads. +- `meshmind/tests/test_grpc_runtime.py`: spin up the asyncio gRPC server helpers to confirm ingestion/search round-trips work over the network and that cancellation shuts servers down cleanly. +- `meshmind/tests/test_setup_scripts.py`: run the provisioning scripts in validation mode to ensure optional dependencies are declared in `pyproject.toml`, that `uv` is bootstrapped, and that skip flags behave as expected. +- `meshmind/tests/test_cli_admin.py`: verify administrative CLI commands use the correct driver factory, settings, and counts reporting. +- `meshmind/tests/test_counts_smoke.py`: exercise the REST `/memories/counts` endpoint and `meshmind admin counts` command against the in-memory driver as smoke coverage. +- `meshmind/tests/test_tasks_scheduled.py`: assert maintenance consolidation retries handle transient conflicts via configurable backoff. +- `meshmind/tests/test_docs_guard.py`: ensure the documentation guard script enforces wiki updates when code modules change. +- `meshmind/tests/test_observability.py`: confirm telemetry metrics/counters update during preprocessing steps. +- `meshmind/tests/test_benchmark_scripts.py`: smoke the benchmarking CLI utilities (`scripts/evaluate_importance.py`, `scripts/consolidation_benchmark.py`, `scripts/benchmark_pagination.py`). +- `meshmind/tests/test_api_examples.py`: validate the documented curl/grpcurl payloads against the FastAPI app and gRPC stub. +- `meshmind/tests/test_protos_packaging.py`: guarantee that the canonical proto file ships with the distribution and still defines the expected service endpoints. + +## Fakes & Fixtures + +- `meshmind.testing.fakes`: supplies fake Memgraph/Redis/embedding drivers and a provider-agnostic `FakeLLMClient` that records + override dictionaries (`llm_models`, `llm_base_urls`, `llm_api_key`) so service tests can assert cascading behaviour without + real SDK calls. +- `meshmind/tests/conftest.py`: defines reusable fixtures such as `dummy_encoder`, `memory_service`, and telemetry reset + helpers. +- `DUMMIES.md`: outlines every compatibility shim and stub so test suites can migrate toward real dependencies over time. + +## Running Tests + +```bash +pip install -e .[dev,docs,testing] +pytest +``` + +Optional extras: + +- `PYTHONPATH=.` ensures imports resolve when running tests manually. +- To test the Neo4j/Memgraph drivers, set `GRAPH_BACKEND` appropriately, point `MEMGRAPH_URI` / `NEO4J_URI` at the Docker + services, and export credentials as described in `SETUP.md` and `ENVIRONMENT_NEEDS.md`. + +## Benchmarking Scripts + +MeshMind ships synthetic benchmarks that exercise the importance heuristic, consolidation planner, and graph pagination +behaviour. Run them locally with the provided Make target: + +```bash +make benchmarks +``` + +The command stores JSON summaries under `build/benchmarks/`: + +- `importance.json` – descriptive statistics for the heuristic across synthetic memories. +- `consolidation.json` – retry counts, removals, and latency distributions for consolidation batches. +- `pagination.json` – pagination duration and fetch counts for the configured graph backend (defaults to the in-memory driver). + +Adjust the script flags (for example `--backend`, `--iterations`, or `--count`) to stress alternative drivers or larger +datasets; see `scripts/*.py` for supported options. Document notable findings in `FINDINGS.md` or `ENVIRONMENT_NEEDS.md` +when tuning defaults for new environments. + +## Adding Tests + +1. Prefer deterministic unit tests using the in-memory driver or fakes. +2. When touching new modules, add coverage in the closest existing test file or create a dedicated module under + `meshmind/tests`. +3. Mock optional dependencies (`LLMClient` providers, Redis, Celery) unless integration coverage is explicitly required. +4. Update documentation (`docs/testing.md`, `README.md`) whenever the testing workflow changes. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..fe4e5a9 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,32 @@ +# Troubleshooting + +## Optional Tooling Installation Failures + +- **Ruff, Pyright, Typeguard** + - Install via `uv pip install ruff pyright typeguard` to match CI tooling. + - If Pyright is unavailable, run `make lint` to exercise Ruff only and document the gap in `ENVIRONMENT_NEEDS.md`. +- **toml-sort / yamllint** + - Provided through `uv pip install toml-sort yamllint`. + - When installing globally, ensure the binaries are on the `PATH` or invoke via `python -m toml_sort` and `python -m yamllint`. + +## Graph Backends + +- **Memgraph / Neo4j connectivity** + - Verify credentials are exported (`MEMGRAPH_URI`, `NEO4J_URI`, etc.) before running `mesh admin graph --backend neo4j`. + - When the native drivers are unavailable, fall back to the in-memory or SQLite backends and note the blocker in `ENVIRONMENT_NEEDS.md`. + +## External Services + +- **Redis** + - Use `docker-compose up redis` to start the local stack, or swap in the `FakeRedisBroker` for unit tests. +- **Embedding providers** + - When `LLM_API_KEY`/`OPENAI_API_KEY` are missing, configure `FAKE_EMBEDDINGS=1` to rely on the deterministic fake encoder + or point `LLM_EMBEDDING_MODEL`/`LLM_EMBEDDING_BASE_URL` at a provider accessible in your environment. + +## Common Runtime Symptoms + +- **Search returns empty results** + - Confirm that the namespace and `entity_labels` filters align with persisted memories. + - For graph-backed searches, run `mesh admin counts` to validate stored entity counts. +- **Pagination behaves unexpectedly** + - Inspect driver-specific logs (enable via `MESH_LOG_LEVEL=DEBUG`) to confirm the offset and limit values passed downstream. diff --git a/examples/extract_preprocess_store_example.py b/examples/extract_preprocess_store_example.py index 74ac65d..ba803d1 100644 --- a/examples/extract_preprocess_store_example.py +++ b/examples/extract_preprocess_store_example.py @@ -1,40 +1,73 @@ -""" -Example flow: extract → preprocess → store using Meshmind pipeline. -Requires a running Memgraph instance and a valid OPENAI_API_KEY. -""" -from meshmind.core.types import Memory +"""End-to-end MeshMind example covering extraction, storage, and retrieval.""" +from __future__ import annotations + from meshmind.client import MeshMind +from meshmind.core.types import Memory, Triplet + -def main(): - # Initialize MeshMind client (uses OpenAI and default MemgraphDriver) +def main() -> None: mm = MeshMind() - driver = mm.driver - # Sample content for extraction texts = [ "The Eiffel Tower is located in Paris and was built in 1889.", - "Python is a programming language created by Guido van Rossum." + "Python is a programming language created by Guido van Rossum.", ] - # Extract memories via LLM memories = mm.extract_memories( instructions="Extract key facts as Memory objects.", namespace="demo", - entity_types=[Memory], + entity_types=[Memory], content=texts, ) - print(f"Extracted {len(memories)} memories:") - for m in memories: - print(m.json()) - - # Preprocess: deduplicate, score importance, compress - memories = mm.deduplicate(memories, threshold=0.9) + memories = mm.deduplicate(memories) memories = mm.score_importance(memories) memories = mm.compress(memories) - - # Store into graph mm.store_memories(memories) - print("Memories stored to graph.") + print(f"Stored {len(memories)} memories.") + + if len(memories) >= 2: + relation = Triplet( + subject=str(memories[0].uuid), + predicate="RELATED_TO", + object=str(memories[1].uuid), + namespace="demo", + entity_label="Knowledge", + metadata={"confidence": 0.9}, + ) + mm.store_triplets([relation]) + print("Stored relationship between first two memories.") + + stored = mm.list_memories(namespace="demo", entity_labels=["Memory"], limit=10) + + hits = mm.search("Eiffel Tower", namespace="demo", entity_labels=["Memory"]) + print("Hybrid search results:") + for mem in hits: + print(f"- {mem.name} (importance={mem.importance})") + + vector_hits = mm.search_vector("programming", namespace="demo", entity_labels=["Memory"]) + print("Vector-only search results:") + for mem in vector_hits: + print(f"- {mem.name}") + + regex_hits = mm.search_regex(r"Paris", namespace="demo", entity_labels=["Memory"]) + print("Regex search results:") + for mem in regex_hits: + print(f"- {mem.name}") + + exact_hits = mm.search_exact( + "Python", + namespace="demo", + entity_labels=["Memory"], + fields=["name"], + ) + print("Exact match search results:") + for mem in exact_hits: + print(f"- {mem.name}") + + counts = mm.memory_counts(namespace="demo") + print(f"Filtered view returned {len(stored)} memories from the driver.") + print(f"Namespace counts: {counts}") + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/meshmind/api/grpc.py b/meshmind/api/grpc.py new file mode 100644 index 0000000..dee7a73 --- /dev/null +++ b/meshmind/api/grpc.py @@ -0,0 +1,192 @@ +"""gRPC adapters and helpers for MeshMind.""" +from __future__ import annotations + +from typing import Iterable, Sequence + +from google.protobuf import json_format, struct_pb2, wrappers_pb2 + +from meshmind.api.service import MemoryPayload, MemoryService, SearchPayload, TripletPayload +from meshmind.core.types import Memory, Triplet +from meshmind.protos import memory_service_pb2 as pb2 +from meshmind.protos import memory_service_pb2_grpc as pb2_grpc + +__all__ = [ + "GrpcServiceStub", + "memory_to_proto", + "memory_proto_to_memory", + "triplet_to_proto", + "memory_from_proto", + "triplet_from_proto", +] + + +def _dict_to_struct(data: dict[str, object] | None) -> struct_pb2.Struct: + struct = struct_pb2.Struct() + if data: + struct.update(data) + return struct + + +def _struct_to_dict(struct: struct_pb2.Struct | None) -> dict[str, object]: + if not struct: + return {} + return json_format.MessageToDict(struct, preserving_proto_field_name=True) + + +def _maybe_wrapper(value: object | None, wrapper_cls): + if value is None: + return None + return wrapper_cls(value=value) + + +def memory_to_proto(memory: Memory) -> pb2.MemoryPayload: + return pb2.MemoryPayload( + uuid=str(memory.uuid), + namespace=memory.namespace, + name=memory.name, + entity_label=memory.entity_label, + embedding=list(memory.embedding or []), + metadata=_dict_to_struct(memory.metadata), + reference_time=memory.reference_time.isoformat() if memory.reference_time else "", + importance=_maybe_wrapper(memory.importance, wrappers_pb2.DoubleValue), + ttl_seconds=_maybe_wrapper(memory.ttl_seconds, wrappers_pb2.Int64Value), + created_at=memory.created_at.isoformat() if memory.created_at else "", + updated_at=memory.updated_at.isoformat() if memory.updated_at else "", + ) + + +def memory_from_proto(message: pb2.MemoryPayload) -> MemoryPayload: + data: dict[str, object] = { + "uuid": message.uuid or None, + "namespace": message.namespace, + "name": message.name, + "entity_label": message.entity_label or "Memory", + "embedding": list(message.embedding), + "metadata": _struct_to_dict(message.metadata), + "reference_time": message.reference_time or None, + } + if message.HasField("importance"): + data["importance"] = message.importance.value + if message.HasField("ttl_seconds"): + data["ttl_seconds"] = int(message.ttl_seconds.value) + payload = MemoryPayload(**{k: v for k, v in data.items() if v is not None}) + return payload + + +def memory_proto_to_memory(message: pb2.MemoryPayload) -> Memory: + payload = memory_from_proto(message) + data = payload.model_dump(exclude_none=True) + if message.created_at: + data["created_at"] = message.created_at + if message.updated_at: + data["updated_at"] = message.updated_at + return Memory(**data) + + +def triplet_to_proto(triplet: Triplet) -> pb2.TripletPayload: + return pb2.TripletPayload( + subject=triplet.subject, + predicate=triplet.predicate, + object=triplet.object, + namespace=triplet.namespace, + entity_label=triplet.entity_label, + metadata=_dict_to_struct(triplet.metadata), + reference_time=triplet.reference_time.isoformat() if triplet.reference_time else "", + ) + + +def triplet_from_proto(message: pb2.TripletPayload) -> TripletPayload: + data: dict[str, object] = { + "subject": message.subject, + "predicate": message.predicate, + "object": message.object, + "namespace": message.namespace, + "entity_label": message.entity_label or "Relation", + "metadata": _struct_to_dict(message.metadata), + "reference_time": message.reference_time or None, + } + return TripletPayload(**data) + + +def _search_payload_from_proto(message: pb2.SearchPayload) -> SearchPayload: + data: dict[str, object] = { + "query": message.query, + "namespace": message.namespace or None, + "top_k": message.top_k or 0, + "encoder": message.encoder or None, + "entity_labels": list(message.entity_labels) or None, + "rerank_model": message.rerank_model or None, + "use_llm_rerank": message.use_llm_rerank, + "llm_models": dict(message.llm_models), + "llm_base_urls": dict(message.llm_base_urls), + "llm_api_key": message.llm_api_key or None, + } + if message.HasField("rerank_k"): + data["rerank_k"] = message.rerank_k.value + payload = SearchPayload(**{k: v for k, v in data.items() if v not in ({}, None)}) + if payload.top_k <= 0: + payload.top_k = SearchPayload.model_fields["top_k"].default # type: ignore[index] + return payload + + +class GrpcServiceStub(pb2_grpc.MeshMindServiceServicer): + """In-process service implementation mirroring the real gRPC server.""" + + def __init__(self, service: MemoryService) -> None: + self.service = service + + # The context parameter is optional to keep the signature compatible with grpc.Servicer methods. + def IngestMemories(self, request: pb2.IngestMemoriesRequest, context=None) -> pb2.IngestMemoriesResponse: # noqa: N802 + payloads = [memory_from_proto(memory) for memory in request.memories] + uuids = self.service.ingest_memories(payloads) + return pb2.IngestMemoriesResponse(uuids=uuids) + + def IngestTriplets(self, request: pb2.IngestTripletsRequest, context=None) -> pb2.IngestTripletsResponse: # noqa: N802 + payloads = [triplet_from_proto(triplet) for triplet in request.triplets] + stored = self.service.ingest_triplets(payloads) + return pb2.IngestTripletsResponse(stored=stored) + + def Search(self, request: pb2.SearchPayload, context=None) -> pb2.SearchResponse: # noqa: N802 + payload = _search_payload_from_proto(request) + results = self.service.search(payload) + return pb2.SearchResponse(results=[memory_to_proto(mem) for mem in results]) + + def MemoryCounts(self, request: pb2.MemoryCountsRequest, context=None) -> pb2.MemoryCountsResponse: # noqa: N802 + namespace = request.namespace or None + counts = self.service.memory_counts(namespace) + response = pb2.MemoryCountsResponse() + for ns, labels in counts.items(): + entry = response.counts[ns] + for label, value in labels.items(): + entry.entity_counts[label] = int(value) + return response + + # Convenience helpers ------------------------------------------------- + def ingest_memories(self, payloads: Iterable[MemoryPayload]) -> Sequence[str]: + request = pb2.IngestMemoriesRequest(memories=[memory_to_proto(p.to_memory()) for p in payloads]) + response = self.IngestMemories(request) + return list(response.uuids) + + def ingest_triplets(self, payloads: Iterable[TripletPayload]) -> int: + request = pb2.IngestTripletsRequest(triplets=[triplet_to_proto(p.to_triplet()) for p in payloads]) + response = self.IngestTriplets(request) + return response.stored + + def search(self, payload: SearchPayload) -> Sequence[Memory]: + proto = pb2.SearchPayload( + query=payload.query, + namespace=payload.namespace or "", + top_k=payload.top_k, + encoder=payload.encoder or "", + entity_labels=list(payload.entity_labels or []), + rerank_model=payload.rerank_model or "", + use_llm_rerank=payload.use_llm_rerank, + llm_models=dict(payload.llm_models or {}), + llm_base_urls=dict(payload.llm_base_urls or {}), + llm_api_key=payload.llm_api_key or "", + ) + if payload.rerank_k is not None: + proto.rerank_k.CopyFrom(wrappers_pb2.Int32Value(value=payload.rerank_k)) + response = self.Search(proto) + memories = [memory_proto_to_memory(memory) for memory in response.results] + return memories diff --git a/meshmind/api/grpc_server.py b/meshmind/api/grpc_server.py new file mode 100644 index 0000000..65f51e1 --- /dev/null +++ b/meshmind/api/grpc_server.py @@ -0,0 +1,98 @@ +"""Runtime gRPC server utilities for MeshMind.""" +from __future__ import annotations + +import asyncio +import logging +from collections.abc import Iterable, Sequence +from typing import Any + +import grpc + +from meshmind.api.grpc import GrpcServiceStub +from meshmind.api.service import MemoryService +from meshmind.protos import memory_service_pb2_grpc as pb2_grpc + +__all__ = [ + "create_server", + "serve", + "serve_forever", +] + + +def create_server( + memory_service: MemoryService, + *, + host: str = "0.0.0.0", + port: int = 50051, + interceptors: Iterable[grpc.ServerInterceptor] | None = None, + options: Sequence[tuple[str, Any]] | None = None, +) -> tuple[grpc.aio.Server, int]: + """Instantiate a gRPC server for the provided service. + + Returns the server instance and the bound port. Passing ``port=0`` allows the + OS to select a free port which is reported in the return value. + """ + + server = grpc.aio.server( + interceptors=list(interceptors or []), + options=list(options or []), + ) + pb2_grpc.add_MeshMindServiceServicer_to_server(GrpcServiceStub(memory_service), server) + bound_port = server.add_insecure_port(f"{host}:{port}") + if bound_port == 0: + raise RuntimeError("Failed to bind MeshMind gRPC server port") + return server, bound_port + + +async def serve( + memory_service: MemoryService, + *, + host: str = "0.0.0.0", + port: int = 50051, + interceptors: Iterable[grpc.ServerInterceptor] | None = None, + options: Sequence[tuple[str, Any]] | None = None, + shutdown_grace: float = 5.0, + startup_event: asyncio.Event | None = None, +) -> None: + """Run the gRPC server until cancellation.""" + + server, bound_port = create_server( + memory_service, + host=host, + port=port, + interceptors=interceptors, + options=options, + ) + await server.start() + logging.getLogger(__name__).info( + "MeshMind gRPC server listening on %s:%s", host, bound_port + ) + if startup_event is not None: + startup_event.set() + try: + await server.wait_for_termination() + finally: + await server.stop(shutdown_grace) + + +def serve_forever( + memory_service: MemoryService, + *, + host: str = "0.0.0.0", + port: int = 50051, + interceptors: Iterable[grpc.ServerInterceptor] | None = None, + options: Sequence[tuple[str, Any]] | None = None, + shutdown_grace: float = 5.0, +) -> None: + """Start the asynchronous server and block the current thread.""" + + asyncio.run( + serve( + memory_service, + host=host, + port=port, + interceptors=interceptors, + options=options, + shutdown_grace=shutdown_grace, + ) + ) diff --git a/meshmind/api/memory_manager.py b/meshmind/api/memory_manager.py index 935d270..d06c440 100644 --- a/meshmind/api/memory_manager.py +++ b/meshmind/api/memory_manager.py @@ -1,39 +1,50 @@ -from typing import Any, List, Optional +from __future__ import annotations + +from typing import Any, Dict, List, Optional, Sequence from uuid import UUID +from pydantic import BaseModel + +from meshmind.core.types import Memory, Triplet + + class MemoryManager: - """ - Mid-level CRUD interface for Memory objects, delegating to an underlying graph driver. - """ + """Mid-level CRUD interface for ``Memory`` and ``Triplet`` objects.""" + def __init__(self, graph_driver: Any): # pragma: no cover self.driver = graph_driver - def add_memory(self, memory: Any) -> UUID: + @staticmethod + def _props(model: Any) -> Dict[str, Any]: + if isinstance(model, BaseModel): + return model.model_dump(exclude_none=True) + if hasattr(model, "dict") or hasattr(model, "model_dump"): + try: + return model.model_dump(exclude_none=True) # type: ignore[attr-defined] + except (TypeError, AttributeError): + pass + if isinstance(model, dict): + return {k: v for k, v in model.items() if v is not None} + return {k: v for k, v in model.__dict__.items() if v is not None} + + def add_memory(self, memory: Memory) -> UUID: """ Add a new Memory object to the graph. :param memory: A Memory-like object to be stored. :return: The UUID of the newly added memory. """ - # Upsert the memory object into the graph - try: - props = memory.dict(exclude_none=True) - except Exception: - props = memory.__dict__ + props = self._props(memory) self.driver.upsert_entity(memory.entity_label, memory.name, props) return memory.uuid - def update_memory(self, memory: Any) -> None: + def update_memory(self, memory: Memory) -> None: """ Update an existing Memory object in the graph. :param memory: A Memory-like object with updated fields. """ - # Update an existing memory via upsert - try: - props = memory.dict(exclude_none=True) - except Exception: - props = memory.__dict__ + props = self._props(memory) self.driver.upsert_entity(memory.entity_label, memory.name, props) def delete_memory(self, memory_id: UUID) -> None: @@ -52,44 +63,104 @@ def get_memory(self, memory_id: UUID) -> Optional[Any]: :param memory_id: UUID of the memory to retrieve. :return: Memory-like object or None if not found. """ - # Retrieve a memory by UUID - from meshmind.core.types import Memory - - cypher = "MATCH (m) WHERE m.uuid = $uuid RETURN m" - params = {"uuid": str(memory_id)} - records = self.driver.find(cypher, params) - if not records: + payload = self.driver.get_entity(str(memory_id)) + if not payload: return None - # Extract node properties - record = records[0] - data = record.get('m', record) try: - return Memory(**data) + return Memory(**payload) except Exception: return None - def list_memories(self, namespace: Optional[str] = None) -> List[Any]: + def list_memories( + self, + namespace: Optional[str] = None, + entity_labels: Optional[Sequence[str]] = None, + *, + offset: int = 0, + limit: Optional[int] = None, + query: Optional[str] = None, + use_search: bool | None = None, + ) -> List[Memory]: """ - List Memory objects, optionally filtered by namespace. + List Memory objects, optionally filtered by namespace and entity label. :param namespace: If provided, only return memories in this namespace. + :param entity_labels: Optional entity labels to restrict the results. + :param offset: Offset into the result set for pagination. + :param limit: Maximum number of memories to return. + :param query: Optional search string to hint server-side filtering. + :param use_search: Force enabling/disabling the driver's search routine. :return: List of Memory-like objects. """ - # List memories, optionally filtered by namespace - from meshmind.core.types import Memory - - if namespace: - cypher = "MATCH (m) WHERE m.namespace = $namespace RETURN m" - params = {"namespace": namespace} + should_search = use_search if use_search is not None else bool(query) + if should_search and hasattr(self.driver, "search_entities"): + search_fn = getattr(self.driver, "search_entities") + records = search_fn( # type: ignore[operator] + query=query, + namespace=namespace, + entity_labels=entity_labels, + offset=offset, + limit=limit, + ) else: - cypher = "MATCH (m) RETURN m" - params = {} - records = self.driver.find(cypher, params) - result: List[Any] = [] - for record in records: - data = record.get('m', record) + records = self.driver.list_entities( + namespace, + entity_labels, + offset=offset, + limit=limit, + ) + result: List[Memory] = [] + for data in records: try: result.append(Memory(**data)) except Exception: continue - return result \ No newline at end of file + return result + + def count_memories(self, namespace: Optional[str] = None) -> Dict[str, Dict[str, int]]: + """Aggregate memory counts grouped by namespace and entity label.""" + + if not hasattr(self.driver, "count_entities"): + raise NotImplementedError("Graph driver does not implement count_entities") + counter = getattr(self.driver, "count_entities") + return counter(namespace) # type: ignore[misc] + + def add_triplet(self, triplet: Triplet) -> None: + """Persist or update a ``Triplet`` relationship.""" + + props = self._props(triplet) + namespace = props.pop("namespace", None) + if namespace is not None: + props["namespace"] = namespace + self.driver.upsert_edge( + triplet.subject, + triplet.predicate, + triplet.object, + props, + ) + + def delete_triplet(self, subj: str, predicate: str, obj: str) -> None: + """Remove a relationship identified by subject/predicate/object.""" + + self.driver.delete_triplet(subj, predicate, obj) + + def list_triplets(self, namespace: Optional[str] = None) -> List[Triplet]: + """Return stored ``Triplet`` objects, optionally filtered by namespace.""" + + records = self.driver.list_triplets(namespace) + result: List[Triplet] = [] + for record in records: + data = { + "subject": record.get("subject"), + "predicate": record.get("predicate"), + "object": record.get("object"), + "namespace": record.get("namespace") or namespace, + "entity_label": record.get("predicate", "Relation"), + "metadata": record.get("metadata") or {}, + "reference_time": record.get("reference_time"), + } + try: + result.append(Triplet(**data)) + except Exception: + continue + return result diff --git a/meshmind/api/rest.py b/meshmind/api/rest.py new file mode 100644 index 0000000..4a8fb23 --- /dev/null +++ b/meshmind/api/rest.py @@ -0,0 +1,123 @@ +"""REST adapters for the :mod:`meshmind` service layer.""" +from __future__ import annotations + +from typing import Any, Dict, Iterable, List + +from meshmind.api.service import MemoryPayload, MemoryService, SearchPayload, TripletPayload + + +class RestAPIStub: + """Fallback handler that emulates REST routes without FastAPI.""" + + def __init__(self, service: MemoryService) -> None: + self.service = service + + def dispatch(self, method: str, path: str, payload: Dict[str, Any] | None = None) -> Dict[str, Any]: + method = method.upper() + payload = payload or {} + if method == "POST" and path == "/memories": + memories = [MemoryPayload(**item) for item in payload.get("memories", [])] + uuids = self.service.ingest_memories(memories) + return {"uuids": uuids} + if method == "POST" and path == "/triplets": + triplets = [TripletPayload(**item) for item in payload.get("triplets", [])] + count = self.service.ingest_triplets(triplets) + return {"stored": count} + if method == "POST" and path == "/search": + request = SearchPayload(**payload) + results = self.service.search(request) + return {"results": [mem.model_dump(exclude_none=True) for mem in results]} + if method == "GET" and path == "/memories": + namespace = payload.get("namespace") + entity_labels = payload.get("entity_labels") + offset = int(payload.get("offset", 0)) + limit_value = payload.get("limit") + limit = int(limit_value) if limit_value is not None else None + query = payload.get("query") + use_search = payload.get("use_search") + memories = self.service.list_memories( + namespace, + entity_labels, + offset=offset, + limit=limit, + query=query, + use_search=use_search, + ) + return {"memories": [mem.model_dump(exclude_none=True) for mem in memories]} + if method == "GET" and path == "/memories/counts": + namespace = payload.get("namespace") + counts = self.service.memory_counts(namespace) + return {"counts": counts} + if method == "GET" and path == "/triplets": + namespace = payload.get("namespace") + triplets = self.service.list_triplets(namespace) + return {"triplets": [triplet.model_dump(exclude_none=True) for triplet in triplets]} + raise ValueError(f"Unsupported route {method} {path}") + + +def create_app(service: MemoryService) -> Any: + """Create a FastAPI application if FastAPI is installed, otherwise return a stub.""" + + try: # pragma: no cover - optional dependency path + from fastapi import FastAPI, HTTPException + except ImportError: # pragma: no cover - executed in tests without fastapi + return RestAPIStub(service) + + app = FastAPI(title="MeshMind API") + + @app.post("/memories") + def create_memories(payload: Dict[str, Iterable[Dict[str, Any]]]): + try: + items = [MemoryPayload(**item) for item in payload.get("memories", [])] + except Exception as exc: # pragma: no cover - FastAPI handles validation + raise HTTPException(status_code=400, detail=str(exc)) + uuids = service.ingest_memories(items) + return {"uuids": uuids} + + @app.post("/triplets") + def create_triplets(payload: Dict[str, Iterable[Dict[str, Any]]]): + try: + items = [TripletPayload(**item) for item in payload.get("triplets", [])] + except Exception as exc: # pragma: no cover + raise HTTPException(status_code=400, detail=str(exc)) + stored = service.ingest_triplets(items) + return {"stored": stored} + + @app.post("/search") + def search(payload: Dict[str, Any]): + try: + request = SearchPayload(**payload) + except Exception as exc: # pragma: no cover + raise HTTPException(status_code=400, detail=str(exc)) + results = service.search(request) + return {"results": [mem.model_dump(exclude_none=True) for mem in results]} + + @app.get("/memories") + def list_memories( + namespace: str | None = None, + entity_labels: List[str] | None = None, + offset: int = 0, + limit: int | None = None, + query: str | None = None, + use_search: bool | None = None, + ): + memories = service.list_memories( + namespace, + entity_labels, + offset=offset, + limit=limit, + query=query, + use_search=use_search, + ) + return {"memories": [mem.model_dump(exclude_none=True) for mem in memories]} + + @app.get("/triplets") + def list_triplets(namespace: str | None = None): + triplets = service.list_triplets(namespace) + return {"triplets": [triplet.model_dump(exclude_none=True) for triplet in triplets]} + + @app.get("/memories/counts") + def memory_counts(namespace: str | None = None): + return {"counts": service.memory_counts(namespace)} + + return app diff --git a/meshmind/api/service.py b/meshmind/api/service.py new file mode 100644 index 0000000..5f281c3 --- /dev/null +++ b/meshmind/api/service.py @@ -0,0 +1,211 @@ +"""Service layer abstractions for REST and gRPC adapters.""" +from __future__ import annotations + +from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence + +from pydantic import BaseModel, Field + +from meshmind.api.memory_manager import MemoryManager +from meshmind.core.types import Memory, SearchConfig, Triplet +from meshmind.retrieval import llm_rerank, search as retrieval_search + + +class MemoryPayload(BaseModel): + """Serializable payload for creating or updating a memory.""" + + uuid: str | None = None + namespace: str + name: str + entity_label: str = "Memory" + embedding: List[float] | None = None + metadata: dict[str, object] = Field(default_factory=dict) + reference_time: str | None = None + importance: float | None = None + ttl_seconds: int | None = None + + def to_memory(self) -> Memory: + payload = self.model_dump(exclude_none=True) + if self.uuid: + payload["uuid"] = self.uuid + return Memory(**payload) + + +class TripletPayload(BaseModel): + subject: str + predicate: str + object: str + namespace: str + entity_label: str = "Relation" + metadata: dict[str, object] = Field(default_factory=dict) + reference_time: str | None = None + + def to_triplet(self) -> Triplet: + return Triplet(**self.model_dump(exclude_none=True)) + + +class SearchPayload(BaseModel): + query: str + namespace: str | None = None + top_k: int = 10 + encoder: str | None = None + rerank_k: int | None = None + entity_labels: Sequence[str] | None = None + rerank_model: str | None = None + use_llm_rerank: bool = False + llm_models: Dict[str, str] | None = None + llm_base_urls: Dict[str, Optional[str]] | None = None + llm_api_key: str | None = None + + def to_config(self) -> SearchConfig: + config = SearchConfig(top_k=self.top_k) + if self.encoder: + config.encoder = self.encoder + if self.rerank_k is not None: + config.rerank_k = self.rerank_k + if self.rerank_model: + config.rerank_model = self.rerank_model + elif self.llm_models and self.llm_models.get("rerank"): + config.rerank_model = self.llm_models["rerank"] + return config + + def llm_override_kwargs(self) -> Dict[str, Any]: + return { + "models": self.llm_models, + "base_urls": self.llm_base_urls, + "api_key": self.llm_api_key, + } + + +class MemoryService: + """Business logic for ingestion, retrieval, and triplet persistence.""" + + def __init__( + self, + manager: MemoryManager, + *, + llm_client: Any | None = None, + llm_client_factory: Callable[[], Any] | None = None, + ) -> None: + self.manager = manager + self._llm_client: Any | None = llm_client + self._llm_client_factory = llm_client_factory + + @property + def llm_client(self) -> Any | None: + """Expose the cached LLM client (mainly for tests).""" + + return self._llm_client + + def _resolve_llm_client(self) -> Any | None: + if self._llm_client is not None: + return self._llm_client + if self._llm_client_factory is None: + return None + try: + self._llm_client = self._llm_client_factory() + except Exception: + return None + return self._llm_client + + # ------------------------------------------------------------------ + # Ingestion + # ------------------------------------------------------------------ + def ingest_memories(self, payloads: Sequence[MemoryPayload]) -> List[str]: + uuids: List[str] = [] + for payload in payloads: + memory = payload.to_memory() + self.manager.add_memory(memory) + uuids.append(str(memory.uuid)) + return uuids + + def ingest_triplets(self, payloads: Iterable[TripletPayload]) -> int: + count = 0 + for payload in payloads: + self.manager.add_triplet(payload.to_triplet()) + count += 1 + return count + + # ------------------------------------------------------------------ + # Retrieval + # ------------------------------------------------------------------ + def search(self, request: SearchPayload) -> List[Memory]: + config = request.to_config() + candidate_limit = max(config.top_k * 5, (config.rerank_k or 0) * 2) + limit = candidate_limit or None + memories = self.manager.list_memories( + namespace=request.namespace, + entity_labels=request.entity_labels, + limit=limit, + query=request.query, + use_search=True, + ) + reranker = None + if request.use_llm_rerank: + llm_client = self._resolve_llm_client() + if llm_client is not None: + overrides = request.llm_override_kwargs() + if any(overrides.values()): + with_overrides = getattr(llm_client, "with_overrides", None) + if callable(with_overrides): + llm_client = with_overrides(**overrides) + endpoint_override = None + if request.llm_base_urls: + endpoint_override = ( + request.llm_base_urls.get("rerank") + or request.llm_base_urls.get("default") + ) + rerank_model = config.rerank_model + + def _reranker( + query: str, + candidates: Sequence[Memory], + top_k: int, + *, + client: Any = llm_client, + model: str | None = rerank_model, + endpoint: str | None = endpoint_override, + ) -> Sequence[Memory]: + return llm_rerank( + query, + candidates, + client, + top_k, + model=model, + endpoint=endpoint, + ) + + reranker = _reranker + return retrieval_search( + request.query, + memories, + config=config, + reranker=reranker, + ) + + # ------------------------------------------------------------------ + # CRUD proxies + # ------------------------------------------------------------------ + def list_memories( + self, + namespace: str | None = None, + entity_labels: Sequence[str] | None = None, + *, + offset: int = 0, + limit: int | None = None, + query: str | None = None, + use_search: bool | None = None, + ) -> List[Memory]: + return self.manager.list_memories( + namespace, + entity_labels, + offset=offset, + limit=limit, + query=query, + use_search=use_search, + ) + + def list_triplets(self, namespace: str | None = None) -> List[Triplet]: + return self.manager.list_triplets(namespace) + + def memory_counts(self, namespace: str | None = None) -> Dict[str, Dict[str, int]]: + return self.manager.count_memories(namespace) diff --git a/meshmind/cli/__main__.py b/meshmind/cli/__main__.py index 24dab53..0b39cca 100644 --- a/meshmind/cli/__main__.py +++ b/meshmind/cli/__main__.py @@ -5,7 +5,10 @@ import argparse import sys +from meshmind.cli.admin import register_admin_subcommands from meshmind.cli.ingest import ingest_command +from meshmind.core.bootstrap import bootstrap_encoders, bootstrap_entities +from meshmind.core.config import settings def main(): @@ -25,22 +28,74 @@ def main(): "-e", "--embedding-model", default=None, help="Embedding model to use (default from settings)" ) + ingest_parser.add_argument( + "--embedding-endpoint", + default=None, + help="Override the endpoint URL for embedding requests", + ) ingest_parser.add_argument( "-i", "--instructions", default="Extract key facts as Memory objects.", help="Instructions for the extraction LLM prompt" ) + ingest_parser.add_argument( + "--llm-base-url", + default=None, + help="Override the base URL for the LLM provider", + ) + ingest_parser.add_argument( + "--llm-api-key", + default=None, + help="Override the API key used for LLM calls", + ) + ingest_parser.add_argument( + "--extraction-model", + default=None, + help="Model identifier for the extraction step", + ) + ingest_parser.add_argument( + "--extraction-endpoint", + default=None, + help="Endpoint URL for extraction requests", + ) + ingest_parser.add_argument( + "--rerank-model", + default=None, + help="Model identifier for reranking", + ) + ingest_parser.add_argument( + "--rerank-endpoint", + default=None, + help="Endpoint URL for reranking requests", + ) ingest_parser.add_argument( "paths", nargs="+", help="Paths to files or directories to ingest" ) + ingest_parser.set_defaults(func=ingest_command) + + register_admin_subcommands(subparsers) args = parser.parse_args() - if args.command == "ingest": - ingest_command(args) - else: - parser.print_help() - sys.exit(1) + # Ensure default encoders and entities are registered before executing commands + bootstrap_entities() + bootstrap_encoders() + + missing = settings.missing() + if missing: + for group, keys in missing.items(): + print( + f"Warning: missing configuration for {group}: {', '.join(keys)}", + file=sys.stderr, + ) + + func = getattr(args, "func", None) + if callable(func): + result = func(args) + return result + + parser.print_help() + sys.exit(1) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/meshmind/cli/admin.py b/meshmind/cli/admin.py new file mode 100644 index 0000000..e9b818b --- /dev/null +++ b/meshmind/cli/admin.py @@ -0,0 +1,188 @@ +"""Administrative CLI helpers for MeshMind.""" +from __future__ import annotations + +import argparse +import json +import sys +from typing import Callable, TextIO + +from meshmind.api.memory_manager import MemoryManager +from meshmind.core.config import settings +from meshmind.core.observability import telemetry +from meshmind.db.factory import create_graph_driver +from meshmind.models.registry import PredicateRegistry + + +def register_admin_subcommands(subparsers: argparse._SubParsersAction) -> None: + """Attach admin subcommands to the CLI parser.""" + + admin_parser = subparsers.add_parser("admin", help="Administrative commands") + admin_sub = admin_parser.add_subparsers(dest="admin_command") + + predicate_parser = admin_sub.add_parser( + "predicates", help="Manage the predicate registry" + ) + predicate_parser.add_argument("--list", action="store_true", help="List predicates") + predicate_parser.add_argument("--add", metavar="LABEL", help="Add a predicate label") + predicate_parser.add_argument( + "--remove", metavar="LABEL", help="Remove a predicate label if present" + ) + predicate_parser.set_defaults(func=handle_predicates) + + maintenance_parser = admin_sub.add_parser( + "maintenance", help="Inspect maintenance telemetry" + ) + maintenance_parser.add_argument( + "--reset", action="store_true", help="Reset telemetry after printing" + ) + maintenance_parser.add_argument( + "--max-attempts", + type=int, + default=None, + help="Override MAINTENANCE_MAX_ATTEMPTS for this invocation", + ) + maintenance_parser.add_argument( + "--base-delay", + type=float, + default=None, + help="Override MAINTENANCE_BASE_DELAY_SECONDS (seconds)", + ) + maintenance_parser.add_argument( + "--run", + choices=("expire", "consolidate", "compress"), + help="Execute a maintenance task after applying overrides", + ) + maintenance_parser.set_defaults(func=handle_maintenance) + + graph_parser = admin_sub.add_parser( + "graph", help="Validate graph backend connectivity" + ) + graph_parser.add_argument( + "--backend", + default=settings.GRAPH_BACKEND, + help="Backend to test (memory, sqlite, memgraph, neo4j)", + ) + graph_parser.set_defaults(func=handle_graph_check) + + counts_parser = admin_sub.add_parser( + "counts", help="Summarise memory counts by namespace and entity label" + ) + counts_parser.add_argument( + "--backend", + default=settings.GRAPH_BACKEND, + help="Graph backend to inspect", + ) + counts_parser.add_argument( + "--namespace", + default=None, + help="Optional namespace filter", + ) + counts_parser.set_defaults(func=handle_counts) + + +def handle_predicates(args: argparse.Namespace, stream: TextIO | None = None) -> None: + """Apply predicate registry operations based on CLI flags.""" + + stream = stream or sys.stdout + updated = False + if args.add: + PredicateRegistry.add(args.add) + print(f"Registered predicate: {args.add}", file=stream) + updated = True + if args.remove: + removed = PredicateRegistry.remove(args.remove) + status = "Removed" if removed else "Not present" + print(f"{status} predicate: {args.remove}", file=stream) + updated = True + if args.list or not updated: + labels = sorted(PredicateRegistry.all()) + print(json.dumps({"predicates": labels}, indent=2), file=stream) + + +def handle_maintenance(args: argparse.Namespace, stream: TextIO | None = None) -> None: + """Print maintenance telemetry and optionally reset it.""" + + stream = stream or sys.stdout + overrides: dict[str, float] = {} + if getattr(args, "max_attempts", None) is not None: + value = max(int(args.max_attempts), 1) + settings.MAINTENANCE_MAX_ATTEMPTS = value + overrides["MAINTENANCE_MAX_ATTEMPTS"] = float(value) + if getattr(args, "base_delay", None) is not None: + value = max(float(args.base_delay), 0.0) + settings.MAINTENANCE_BASE_DELAY_SECONDS = value + overrides["MAINTENANCE_BASE_DELAY_SECONDS"] = value + + snapshot = telemetry.snapshot() + if overrides: + snapshot.setdefault("overrides", {}).update(overrides) + + print(json.dumps(snapshot, indent=2, sort_keys=True), file=stream) + if args.reset: + telemetry.reset() + print("Telemetry reset", file=stream) + + if getattr(args, "run", None): + from meshmind.tasks import scheduled + + operations: dict[str, Callable[[], object]] = { + "expire": scheduled.expire_task, + "consolidate": scheduled.consolidate_task, + "compress": scheduled.compress_task, + } + result = operations[args.run]() + print(json.dumps({"task": args.run, "result": result}, indent=2, sort_keys=True), file=stream) + + +def handle_graph_check(args: argparse.Namespace, stream: TextIO | None = None) -> int: + """Try to instantiate the requested graph driver and verify connectivity.""" + + stream = stream or sys.stdout + try: + driver = create_graph_driver(backend=args.backend) + except Exception as exc: # pragma: no cover - best effort logging + print(f"Failed to create driver: {exc}", file=sys.stderr) + return 1 + + verify = getattr(driver, "verify_connectivity", None) + if callable(verify): + try: + ok = bool(verify()) + except Exception as exc: # pragma: no cover - depends on backend state + print(f"Connectivity check failed: {exc}", file=sys.stderr) + return 2 + else: + result = "ok" if ok else "unknown" + print(json.dumps({"backend": args.backend, "status": result}), file=stream) + return 0 + + print( + json.dumps({"backend": args.backend, "status": "unsupported"}), + file=stream, + ) + return 0 + + +def handle_counts(args: argparse.Namespace, stream: TextIO | None = None) -> int: + """Print aggregated memory counts grouped by namespace and label.""" + + stream = stream or sys.stdout + try: + driver = create_graph_driver(backend=args.backend) + except Exception as exc: # pragma: no cover - backend instantiation failures + print(f"Failed to create driver: {exc}", file=sys.stderr) + return 1 + + manager = MemoryManager(driver) + try: + counts = manager.count_memories(namespace=args.namespace) + except NotImplementedError as exc: + print(str(exc), file=sys.stderr) + return 2 + finally: + closer = getattr(driver, "close", None) + if callable(closer): # pragma: no cover - depends on backend + closer() + + print(json.dumps(counts, indent=2, sort_keys=True), file=stream) + return 0 diff --git a/meshmind/cli/ingest.py b/meshmind/cli/ingest.py index bfedfa9..60b5113 100644 --- a/meshmind/cli/ingest.py +++ b/meshmind/cli/ingest.py @@ -1,11 +1,14 @@ """ CLI ingest command: load files/folders → extract → preprocess → store. """ +from __future__ import annotations import os import sys from meshmind.client import MeshMind +from meshmind.core.config import settings from meshmind.core.types import Memory +from meshmind.llm_client import build_llm_config_from_settings def ingest_command(args): """ @@ -35,8 +38,35 @@ def ingest_command(args): print("No content found to ingest.", file=sys.stderr) sys.exit(1) + models_override: dict[str, str] = {} + base_urls_override: dict[str, str] = {} + if getattr(args, "embedding_model", None): + models_override["embedding"] = args.embedding_model + if getattr(args, "extraction_model", None): + models_override["extraction"] = args.extraction_model + if getattr(args, "rerank_model", None): + models_override["rerank"] = args.rerank_model + + if getattr(args, "llm_base_url", None): + base_urls_override["default"] = args.llm_base_url + if getattr(args, "embedding_endpoint", None): + base_urls_override["embedding"] = args.embedding_endpoint + if getattr(args, "extraction_endpoint", None): + base_urls_override["extraction"] = args.extraction_endpoint + if getattr(args, "rerank_endpoint", None): + base_urls_override["rerank"] = args.rerank_endpoint + + overrides_models = models_override or None + overrides_base = base_urls_override or None + api_key_override = getattr(args, "llm_api_key", None) or None + llm_config = build_llm_config_from_settings(settings).override( + models=overrides_models, + base_urls=overrides_base, + api_key=api_key_override, + ) + # Extraction - mm = MeshMind() + mm = MeshMind(llm_config=llm_config) memories = mm.extract_memories( instructions=args.instructions, namespace=args.namespace, diff --git a/meshmind/client.py b/meshmind/client.py index 365eac0..8feb1fb 100644 --- a/meshmind/client.py +++ b/meshmind/client.py @@ -1,80 +1,335 @@ -""" -MeshMind client combining LLM, embedding, and graph driver. -""" -from openai import OpenAI -from typing import Any, List, Type -from meshmind.db.memgraph_driver import MemgraphDriver +"""High-level MeshMind client orchestrating ingestion and storage flows.""" +from __future__ import annotations + +from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Type +from uuid import UUID + +from meshmind.api.memory_manager import MemoryManager +from meshmind.core.bootstrap import bootstrap_entities, bootstrap_encoders from meshmind.core.config import settings +from meshmind.core.types import Memory, Triplet, SearchConfig +from meshmind.db.base_driver import GraphDriver +from meshmind.db.factory import graph_driver_factory as make_graph_driver_factory +from meshmind.models.registry import EntityRegistry, PredicateRegistry +from meshmind.llm_client import LLMClient, LLMConfig, build_llm_config_from_settings class MeshMind: - """ - High-level client to manage extraction, preprocessing, and storage of memories. - """ + """High-level orchestration client for extraction, preprocessing, and persistence.""" + def __init__( self, llm_client: Any = None, + llm_config: LLMConfig | None = None, embedding_model: str | None = None, - graph_driver: Any = None, + graph_driver: Optional[GraphDriver] = None, + graph_driver_factory: Callable[[], GraphDriver] | None = None, ): - # Initialize LLM client - self.llm_client = llm_client or OpenAI() - # Set embedding model name - self.embedding_model = embedding_model or settings.EMBEDDING_MODEL - # Initialize graph driver - self.driver = graph_driver or MemgraphDriver( - settings.MEMGRAPH_URI, - settings.MEMGRAPH_USERNAME, - settings.MEMGRAPH_PASSWORD, + config = llm_config or build_llm_config_from_settings(settings) + if embedding_model: + config = config.override(models={"embedding": embedding_model}) + self.llm_config = config + + if llm_client is None: + llm_client = LLMClient(config) + + self.llm_client = llm_client + self.embedding_model = config.model_for("embedding", fallback=settings.EMBEDDING_MODEL) + + self._graph_driver: Optional[GraphDriver] = graph_driver + self._graph_driver_factory = graph_driver_factory + if self._graph_driver is None and self._graph_driver_factory is None: + self._graph_driver_factory = make_graph_driver_factory() + + self._memory_manager: Optional[MemoryManager] = ( + MemoryManager(self._graph_driver) if self._graph_driver else None ) + self.entity_registry = EntityRegistry + self.predicate_registry = PredicateRegistry + bootstrap_entities([Memory]) + bootstrap_encoders() + @property + def graph_driver(self) -> GraphDriver: + """Expose the active graph driver, creating it on demand.""" + return self._ensure_driver() + + @property + def driver(self) -> GraphDriver: + """Backward compatible alias for :attr:`graph_driver`.""" + return self.graph_driver + + # ------------------------------------------------------------------ + # Internal helpers + # ------------------------------------------------------------------ + def _ensure_driver(self) -> GraphDriver: + if self._graph_driver is None: + if self._graph_driver_factory is None: + raise RuntimeError("No graph driver factory available for MeshMind") + self._graph_driver = self._graph_driver_factory() + return self._graph_driver + + def _ensure_manager(self) -> MemoryManager: + if self._memory_manager is None: + self._memory_manager = MemoryManager(self._ensure_driver()) + return self._memory_manager + + # ------------------------------------------------------------------ + # Pipelines + # ------------------------------------------------------------------ def extract_memories( self, instructions: str, namespace: str, - entity_types: List[Type[Any]], - content: List[str], + entity_types: Sequence[Type[Any]], + content: Sequence[str], ) -> List[Any]: from meshmind.pipeline.extract import extract_memories return extract_memories( instructions=instructions, namespace=namespace, - entity_types=entity_types, + entity_types=list(entity_types), embedding_model=self.embedding_model, - content=content, + content=list(content), llm_client=self.llm_client, ) def deduplicate( self, - memories: List[Any], + memories: Sequence[Any], threshold: float = 0.95, ) -> List[Any]: from meshmind.pipeline.preprocess import deduplicate - return deduplicate(memories, threshold) + return deduplicate(list(memories), threshold) def score_importance( self, - memories: List[Any], + memories: Sequence[Any], ) -> List[Any]: from meshmind.pipeline.preprocess import score_importance - return score_importance(memories) + return score_importance(list(memories)) def compress( self, - memories: List[Any], + memories: Sequence[Any], ) -> List[Any]: from meshmind.pipeline.preprocess import compress - return compress(memories) + return compress(list(memories)) def store_memories( self, - memories: List[Any], + memories: Iterable[Any], ) -> None: from meshmind.pipeline.store import store_memories - store_memories(memories, self.driver) + store_memories( + memories, + self._ensure_driver(), + entity_registry=self.entity_registry, + ) + + def store_triplets( + self, + triplets: Iterable[Triplet], + ) -> None: + from meshmind.pipeline.store import store_triplets + + store_triplets( + triplets, + self._ensure_driver(), + predicate_registry=self.predicate_registry, + ) + + # ------------------------------------------------------------------ + # CRUD helpers + # ------------------------------------------------------------------ + def create_memory(self, memory: Memory) -> UUID: + return self._ensure_manager().add_memory(memory) + + def update_memory(self, memory: Memory) -> None: + self._ensure_manager().update_memory(memory) + + def delete_memory(self, memory_id: UUID) -> None: + self._ensure_manager().delete_memory(memory_id) + + def get_memory(self, memory_id: UUID) -> Optional[Memory]: + return self._ensure_manager().get_memory(memory_id) + + def list_memories( + self, + namespace: str | None = None, + entity_labels: Sequence[str] | None = None, + *, + offset: int = 0, + limit: int | None = None, + query: str | None = None, + use_search: bool | None = None, + ) -> List[Memory]: + return self._ensure_manager().list_memories( + namespace, + entity_labels, + offset=offset, + limit=limit, + query=query, + use_search=use_search, + ) + + def memory_counts(self, namespace: str | None = None) -> Dict[str, Dict[str, int]]: + return self._ensure_manager().count_memories(namespace) + + def create_triplet(self, triplet: Triplet) -> None: + self.predicate_registry.add(triplet.predicate) + self._ensure_manager().add_triplet(triplet) + + def delete_triplet(self, triplet: Triplet) -> None: + self._ensure_manager().delete_triplet( + triplet.subject, triplet.predicate, triplet.object + ) + + def list_triplets(self, namespace: str | None = None) -> List[Triplet]: + return self._ensure_manager().list_triplets(namespace) + + # ------------------------------------------------------------------ + # Retrieval helpers + # ------------------------------------------------------------------ + def search( + self, + query: str, + memories: Sequence[Memory] | None = None, + namespace: str | None = None, + entity_labels: Sequence[str] | None = None, + config: SearchConfig | None = None, + use_llm_rerank: bool = False, + reranker: Callable[[str, Sequence[Memory], int], Sequence[Memory]] | None = None, + ) -> List[Memory]: + from meshmind.retrieval import llm_rerank, search as hybrid_search + from meshmind.retrieval.graph import graph_hybrid_search + + cfg = config or SearchConfig(encoder=self.embedding_model) + if cfg.rerank_model is None: + cfg.rerank_model = self.llm_config.model_for("rerank", fallback=None) + active_reranker = reranker + if use_llm_rerank: + active_reranker = lambda q, c, k: llm_rerank( + q, + c, + self.llm_client, + k, + model=cfg.rerank_model, + endpoint=self.llm_config.base_url_for("rerank"), + ) + + if memories is None: + return graph_hybrid_search( + query, + self._ensure_driver(), + namespace=namespace, + entity_labels=entity_labels, + config=cfg, + reranker=active_reranker, + ) + + return hybrid_search( + query, + list(memories), + namespace=namespace, + entity_labels=list(entity_labels) if entity_labels else None, + config=cfg, + reranker=active_reranker, + ) + + def search_vector( + self, + query: str, + memories: Sequence[Memory] | None = None, + namespace: str | None = None, + entity_labels: Sequence[str] | None = None, + config: SearchConfig | None = None, + ) -> List[Memory]: + from meshmind.retrieval import search_vector + from meshmind.retrieval.graph import graph_vector_search + + cfg = config or SearchConfig(encoder=self.embedding_model) + if memories is None: + return graph_vector_search( + query, + self._ensure_driver(), + namespace=namespace, + entity_labels=entity_labels, + config=cfg, + ) + return search_vector( + query, + list(memories), + namespace=namespace, + entity_labels=list(entity_labels) if entity_labels else None, + config=cfg, + ) + + def search_regex( + self, + pattern: str, + memories: Sequence[Memory] | None = None, + namespace: str | None = None, + entity_labels: Sequence[str] | None = None, + flags: int | None = None, + top_k: int = 10, + ) -> List[Memory]: + from meshmind.retrieval import search_regex + from meshmind.retrieval.graph import graph_regex_search + + if memories is None: + return graph_regex_search( + pattern, + self._ensure_driver(), + namespace=namespace, + entity_labels=entity_labels, + flags=flags, + top_k=top_k, + ) + return search_regex( + pattern, + list(memories), + namespace=namespace, + entity_labels=list(entity_labels) if entity_labels else None, + flags=flags, + top_k=top_k, + ) + + def search_exact( + self, + query: str, + memories: Sequence[Memory] | None = None, + namespace: str | None = None, + entity_labels: Sequence[str] | None = None, + fields: Sequence[str] | None = None, + case_sensitive: bool = False, + top_k: int = 10, + ) -> List[Memory]: + from meshmind.retrieval import search_exact + from meshmind.retrieval.graph import graph_exact_search + + if memories is None: + return graph_exact_search( + query, + self._ensure_driver(), + namespace=namespace, + entity_labels=entity_labels, + fields=fields, + case_sensitive=case_sensitive, + top_k=top_k, + ) + return search_exact( + query, + list(memories), + namespace=namespace, + entity_labels=list(entity_labels) if entity_labels else None, + fields=list(fields) if fields else None, + case_sensitive=case_sensitive, + top_k=top_k, + ) + diff --git a/meshmind/core/bootstrap.py b/meshmind/core/bootstrap.py new file mode 100644 index 0000000..0db5610 --- /dev/null +++ b/meshmind/core/bootstrap.py @@ -0,0 +1,45 @@ +"""Bootstrap helpers for wiring encoders and registries.""" +from __future__ import annotations + +import warnings +from typing import Iterable, Sequence, Type + +from pydantic import BaseModel + +from meshmind.core.config import settings +from meshmind.core.embeddings import EncoderRegistry, OpenAIEmbeddingEncoder +from meshmind.core.types import Memory +from meshmind.models.registry import EntityRegistry, PredicateRegistry + + +def bootstrap_encoders(default_models: Sequence[str] | None = None) -> None: + """Ensure a default set of embedding encoders are registered.""" + + models = list(default_models) if default_models else [settings.EMBEDDING_MODEL] + for model_name in models: + if EncoderRegistry.is_registered(model_name): + continue + try: + EncoderRegistry.register(model_name, OpenAIEmbeddingEncoder(model_name)) + except ImportError as exc: + warnings.warn( + f"Skipping registration of OpenAI encoder '{model_name}': {exc}", + RuntimeWarning, + stacklevel=2, + ) + + +def bootstrap_entities(entity_models: Iterable[Type[BaseModel]] | None = None) -> None: + """Register default entity models used throughout the application.""" + + models = list(entity_models) if entity_models else [Memory] + for model in models: + EntityRegistry.register(model) + + +def register_predicates(predicates: Iterable[str]) -> None: + """Register predicate labels in the global predicate registry.""" + + for predicate in predicates: + PredicateRegistry.add(predicate) + diff --git a/meshmind/core/config.py b/meshmind/core/config.py index 9b14ffd..8b1edbb 100644 --- a/meshmind/core/config.py +++ b/meshmind/core/config.py @@ -13,20 +13,98 @@ class Settings: """Application settings loaded from environment variables.""" + GRAPH_BACKEND: str = os.getenv("GRAPH_BACKEND", "memory") MEMGRAPH_URI: str = os.getenv("MEMGRAPH_URI", "bolt://localhost:7687") MEMGRAPH_USERNAME: str = os.getenv("MEMGRAPH_USERNAME", "") MEMGRAPH_PASSWORD: str = os.getenv("MEMGRAPH_PASSWORD", "") + NEO4J_URI: str = os.getenv("NEO4J_URI", "bolt://localhost:7687") + NEO4J_USERNAME: str = os.getenv("NEO4J_USERNAME", "neo4j") + NEO4J_PASSWORD: str = os.getenv("NEO4J_PASSWORD", "") + SQLITE_PATH: str = os.getenv("SQLITE_PATH", ":memory:") REDIS_URL: str = os.getenv("REDIS_URL", "redis://localhost:6379/0") OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "") - EMBEDDING_MODEL: str = os.getenv("EMBEDDING_MODEL", "text-embedding-3-small") + LLM_API_KEY: str = os.getenv("LLM_API_KEY", OPENAI_API_KEY) + LLM_DEFAULT_MODEL: str = os.getenv("LLM_DEFAULT_MODEL", "gpt-5-nano") + LLM_DEFAULT_BASE_URL: str = os.getenv("LLM_DEFAULT_BASE_URL", "") + LLM_EXTRACTION_MODEL: str = os.getenv("LLM_EXTRACTION_MODEL", "") + LLM_EXTRACTION_BASE_URL: str = os.getenv("LLM_EXTRACTION_BASE_URL", "") + _DEFAULT_EMBEDDING = os.getenv("EMBEDDING_MODEL", "text-embedding-3-small") + LLM_EMBEDDING_MODEL: str = os.getenv("LLM_EMBEDDING_MODEL", _DEFAULT_EMBEDDING) + LLM_EMBEDDING_BASE_URL: str = os.getenv("LLM_EMBEDDING_BASE_URL", "") + LLM_RERANK_MODEL: str = os.getenv("LLM_RERANK_MODEL", "") + LLM_RERANK_BASE_URL: str = os.getenv("LLM_RERANK_BASE_URL", "") + EMBEDDING_MODEL: str = LLM_EMBEDDING_MODEL + MAINTENANCE_MAX_ATTEMPTS: int = int(os.getenv("MAINTENANCE_MAX_ATTEMPTS", "3")) + MAINTENANCE_BASE_DELAY_SECONDS: float = float( + os.getenv("MAINTENANCE_BASE_DELAY_SECONDS", "1.0") + ) + + REQUIRED_GROUPS = { + "memgraph": ("MEMGRAPH_URI",), + "neo4j": ("NEO4J_URI",), + "openai": ("OPENAI_API_KEY",), + "redis": ("REDIS_URL",), + } def __repr__(self) -> str: return ( - f"Settings(MEMGRAPH_URI={self.MEMGRAPH_URI}, " + f"Settings(GRAPH_BACKEND={self.GRAPH_BACKEND}, " + f"MEMGRAPH_URI={self.MEMGRAPH_URI}, " f"MEMGRAPH_USERNAME={self.MEMGRAPH_USERNAME}, " + f"NEO4J_URI={self.NEO4J_URI}, " f"REDIS_URL={self.REDIS_URL}, " f"EMBEDDING_MODEL={self.EMBEDDING_MODEL})" ) + @staticmethod + def _mask(value: str) -> str: + if not value: + return "" + if len(value) <= 4: + return "*" * len(value) + return f"{value[:2]}***{value[-2:]}" + + def missing(self) -> dict[str, list[str]]: + """Return missing environment variables grouped by capability.""" + + missing: dict[str, list[str]] = {} + for group, keys in self.REQUIRED_GROUPS.items(): + if group == "memgraph" and self.GRAPH_BACKEND != "memgraph": + continue + if group == "neo4j" and self.GRAPH_BACKEND != "neo4j": + continue + absent = [key for key in keys if not getattr(self, key)] + if absent: + missing[group] = absent + return missing + + def summary(self) -> dict[str, str]: + """Return a sanitized summary of active configuration values.""" + + return { + "MEMGRAPH_URI": self.MEMGRAPH_URI, + "MEMGRAPH_USERNAME": self.MEMGRAPH_USERNAME, + "MEMGRAPH_PASSWORD": self._mask(self.MEMGRAPH_PASSWORD), + "NEO4J_URI": self.NEO4J_URI, + "NEO4J_USERNAME": self.NEO4J_USERNAME, + "NEO4J_PASSWORD": self._mask(self.NEO4J_PASSWORD), + "GRAPH_BACKEND": self.GRAPH_BACKEND, + "SQLITE_PATH": self.SQLITE_PATH, + "REDIS_URL": self.REDIS_URL, + "OPENAI_API_KEY": self._mask(self.OPENAI_API_KEY), + "LLM_API_KEY": self._mask(self.LLM_API_KEY), + "LLM_DEFAULT_MODEL": self.LLM_DEFAULT_MODEL, + "LLM_DEFAULT_BASE_URL": self.LLM_DEFAULT_BASE_URL or None, + "LLM_EXTRACTION_MODEL": self.LLM_EXTRACTION_MODEL or None, + "LLM_EXTRACTION_BASE_URL": self.LLM_EXTRACTION_BASE_URL or None, + "LLM_EMBEDDING_MODEL": self.LLM_EMBEDDING_MODEL, + "LLM_EMBEDDING_BASE_URL": self.LLM_EMBEDDING_BASE_URL or None, + "LLM_RERANK_MODEL": self.LLM_RERANK_MODEL or None, + "LLM_RERANK_BASE_URL": self.LLM_RERANK_BASE_URL or None, + "EMBEDDING_MODEL": self.EMBEDDING_MODEL, + "MAINTENANCE_MAX_ATTEMPTS": self.MAINTENANCE_MAX_ATTEMPTS, + "MAINTENANCE_BASE_DELAY_SECONDS": self.MAINTENANCE_BASE_DELAY_SECONDS, + } + -settings = Settings() \ No newline at end of file +settings = Settings() diff --git a/meshmind/core/embeddings.py b/meshmind/core/embeddings.py index 84a67e8..a32716f 100644 --- a/meshmind/core/embeddings.py +++ b/meshmind/core/embeddings.py @@ -1,18 +1,15 @@ -""" -Embedding encoders and registry for MeshMind. -""" -from typing import List, Dict, Any +"""Embedding encoder implementations and registry utilities.""" +from __future__ import annotations + import time +from typing import Any, Dict, List -_OPENAI_AVAILABLE = True -try: - from openai import OpenAI - from openai.error import RateLimitError -except ImportError: - _OPENAI_AVAILABLE = False - openai = None # type: ignore - class RateLimitError(Exception): # type: ignore - pass +from meshmind.llm_client import ( + LLMClient, + LLMConfig, + RateLimitError, + build_llm_config_from_settings, +) from .config import settings @@ -26,19 +23,16 @@ def __init__( model_name: str = settings.EMBEDDING_MODEL, max_retries: int = 5, backoff_factor: float = 1.0, + llm_client: LLMClient | None = None, + llm_config: LLMConfig | None = None, ): - if not _OPENAI_AVAILABLE: - raise ImportError( - "openai package is required for OpenAIEmbeddingEncoder" - ) - try: - openai.api_key = settings.OPENAI_API_KEY - except Exception: - pass - - self.llm_client = OpenAI() + config = llm_config or build_llm_config_from_settings(settings) + if model_name: + config = config.override(models={"embedding": model_name}) + resolved_model = config.model_for("embedding", fallback=model_name) + self.llm_client = llm_client or LLMClient(config) self.RateLimitError = RateLimitError - self.model_name = model_name + self.model_name = resolved_model self.max_retries = max_retries self.backoff_factor = backoff_factor @@ -49,14 +43,24 @@ def encode(self, texts: List[str] | str) -> List[List[float]]: """ if isinstance(texts, str): texts = [texts] - + for attempt in range(self.max_retries): try: response = self.llm_client.embeddings.create( + operation="embedding", model=self.model_name, input=texts, ) - return [item['embedding'] for item in response['data']] + data = getattr(response, "data", None) + if data is None: + data = response.get("data", []) # type: ignore[assignment] + embeddings: List[List[float]] = [] + for item in data: + if hasattr(item, "embedding"): + embeddings.append(list(getattr(item, "embedding"))) + else: + embeddings.append(list(item["embedding"])) + return embeddings except self.RateLimitError: time.sleep(self.backoff_factor * (2 ** attempt)) except Exception: @@ -71,7 +75,13 @@ class SentenceTransformerEncoder: Encoder that uses a local SentenceTransformer model. """ def __init__(self, model_name: str): - from sentence_transformers import SentenceTransformer + try: # pragma: no cover - optional dependency + from sentence_transformers import SentenceTransformer + except ImportError as exc: + raise ImportError( + "sentence-transformers is required for SentenceTransformerEncoder." + " Install the optional 'sentence-transformers' extra to enable this encoder." + ) from exc self.model = SentenceTransformer(model_name) @@ -106,4 +116,22 @@ def get(cls, name: str) -> Any: encoder = cls._encoders.get(name) if encoder is None: raise KeyError(f"Encoder '{name}' not found in registry") - return encoder \ No newline at end of file + return encoder + + @classmethod + def is_registered(cls, name: str) -> bool: + """Return True if an encoder ``name`` has been registered.""" + + return name in cls._encoders + + @classmethod + def available(cls) -> List[str]: + """Return the list of registered encoder identifiers.""" + + return list(cls._encoders.keys()) + + @classmethod + def clear(cls) -> None: + """Remove all registered encoders. Intended for testing.""" + + cls._encoders.clear() diff --git a/meshmind/core/observability.py b/meshmind/core/observability.py new file mode 100644 index 0000000..6c36fe1 --- /dev/null +++ b/meshmind/core/observability.py @@ -0,0 +1,80 @@ +"""Shared logging and metrics utilities for MeshMind pipelines.""" +from __future__ import annotations + +import logging +from collections import defaultdict +from contextlib import contextmanager +from time import perf_counter +from typing import Any, Dict, Iterable + +_LOGGER_NAME = "meshmind" +logger = logging.getLogger(_LOGGER_NAME) +if not logger.handlers: # pragma: no cover - avoid duplicate handlers in tests + handler = logging.StreamHandler() + formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s") + handler.setFormatter(formatter) + logger.addHandler(handler) +logger.setLevel(logging.INFO) + + +class Telemetry: + """Lightweight in-memory metrics collector.""" + + def __init__(self) -> None: + self._counters: Dict[str, int] = defaultdict(int) + self._durations: Dict[str, list[float]] = defaultdict(list) + self._gauges: Dict[str, float] = {} + + # ------------------------------------------------------------------ + # Counter helpers + # ------------------------------------------------------------------ + def increment(self, metric: str, value: int = 1) -> None: + self._counters[metric] += value + + def gauge(self, metric: str, value: float) -> None: + self._gauges[metric] = value + + def observe(self, metric: str, value: float) -> None: + self._durations[metric].append(value) + + @contextmanager + def track_duration(self, metric: str): + start = perf_counter() + try: + yield + finally: + elapsed = perf_counter() - start + self.observe(metric, elapsed) + + def extend_counter(self, metric: str, values: Iterable[Any]) -> None: + count = sum(1 for _ in values) + self.increment(metric, count) + + # ------------------------------------------------------------------ + # Snapshot helpers + # ------------------------------------------------------------------ + def snapshot(self) -> Dict[str, Any]: + return { + "counters": dict(self._counters), + "durations": {k: list(v) for k, v in self._durations.items()}, + "gauges": dict(self._gauges), + } + + def reset(self) -> None: + self._counters.clear() + self._durations.clear() + self._gauges.clear() + + +telemetry = Telemetry() + + +def log_event(event: str, **fields: Any) -> None: + """Emit a structured log entry and update a counter for the event.""" + + telemetry.increment(f"events.{event}") + if fields: + formatted = " ".join(f"{key}={value}" for key, value in fields.items()) + logger.info("event=%s %s", event, formatted) + else: + logger.info("event=%s", event) diff --git a/meshmind/core/similarity.py b/meshmind/core/similarity.py index 78fa6c9..7e47130 100644 --- a/meshmind/core/similarity.py +++ b/meshmind/core/similarity.py @@ -1,8 +1,13 @@ -""" -Similarity and distance metrics for MeshMind. -""" +"""Similarity and distance metrics for MeshMind.""" +from __future__ import annotations + +from math import sqrt from typing import Sequence -import numpy as np + +try: # pragma: no cover - optional dependency + import numpy as np +except ImportError: # pragma: no cover - fallback path for test sandboxes + np = None # type: ignore def cosine_similarity(vec1: Sequence[float], vec2: Sequence[float]) -> float: @@ -10,23 +15,38 @@ def cosine_similarity(vec1: Sequence[float], vec2: Sequence[float]) -> float: Compute the cosine similarity between two vectors. Returns 0.0 if either vector has zero magnitude. """ - a = np.array(vec1, dtype=float) - b = np.array(vec2, dtype=float) - if a.shape != b.shape: + if np is not None: + a = np.array(vec1, dtype=float) + b = np.array(vec2, dtype=float) + if a.shape != b.shape: + raise ValueError("Vectors must be the same length") + norm_a = np.linalg.norm(a) + norm_b = np.linalg.norm(b) + if norm_a == 0.0 or norm_b == 0.0: + return 0.0 + return float(np.dot(a, b) / (norm_a * norm_b)) + + if len(vec1) != len(vec2): raise ValueError("Vectors must be the same length") - norm_a = np.linalg.norm(a) - norm_b = np.linalg.norm(b) + dot = sum(float(a) * float(b) for a, b in zip(vec1, vec2)) + norm_a = sqrt(sum(float(a) ** 2 for a in vec1)) + norm_b = sqrt(sum(float(b) ** 2 for b in vec2)) if norm_a == 0.0 or norm_b == 0.0: return 0.0 - return float(np.dot(a, b) / (norm_a * norm_b)) + return float(dot / (norm_a * norm_b)) def euclidean_distance(vec1: Sequence[float], vec2: Sequence[float]) -> float: """ Compute the Euclidean distance between two vectors. """ - a = np.array(vec1, dtype=float) - b = np.array(vec2, dtype=float) - if a.shape != b.shape: + if np is not None: + a = np.array(vec1, dtype=float) + b = np.array(vec2, dtype=float) + if a.shape != b.shape: + raise ValueError("Vectors must be the same length") + return float(np.linalg.norm(a - b)) + + if len(vec1) != len(vec2): raise ValueError("Vectors must be the same length") - return float(np.linalg.norm(a - b)) \ No newline at end of file + return float(sqrt(sum((float(a) - float(b)) ** 2 for a, b in zip(vec1, vec2)))) \ No newline at end of file diff --git a/meshmind/core/types.py b/meshmind/core/types.py index 112d31e..305edff 100644 --- a/meshmind/core/types.py +++ b/meshmind/core/types.py @@ -1,11 +1,17 @@ from __future__ import annotations -from datetime import datetime +from datetime import datetime, timezone from typing import Any, Optional, Tuple from uuid import UUID, uuid4 from pydantic import BaseModel, Field +def _utcnow() -> datetime: + """Return the current UTC time with timezone information.""" + + return datetime.now(timezone.utc) + + class Memory(BaseModel): """ Core memory data structure representing a unit of knowledge. @@ -17,7 +23,7 @@ class Memory(BaseModel): embedding: Optional[list[float]] = None metadata: dict[str, Any] = Field(default_factory=dict) reference_time: Optional[datetime] = None - created_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=_utcnow) updated_at: Optional[datetime] = None importance: Optional[float] = None ttl_seconds: Optional[int] = None @@ -43,5 +49,6 @@ class SearchConfig(BaseModel): encoder: str = "text-embedding-3-small" top_k: int = 20 rerank_k: int = 10 + rerank_model: Optional[str] = None filters: Optional[dict[str, Any]] = None hybrid_weights: Tuple[float, float] = (0.5, 0.5) \ No newline at end of file diff --git a/meshmind/core/utils.py b/meshmind/core/utils.py index 74e90dc..25dfb53 100644 --- a/meshmind/core/utils.py +++ b/meshmind/core/utils.py @@ -1,16 +1,52 @@ -"""Utility functions for MeshMind.""" -import uuid -from datetime import datetime +"""Utility helpers for MeshMind with optional dependency guards.""" +from __future__ import annotations + import hashlib -from typing import Any -import tiktoken +import uuid +from datetime import datetime, timezone +from functools import lru_cache +from typing import Any, Optional + +_TIKTOKEN = None + + +def _ensure_tiktoken() -> Any: + """Return the ``tiktoken`` module if it is installed.""" + + global _TIKTOKEN + if _TIKTOKEN is None: + try: + import tiktoken # type: ignore + except ImportError as exc: # pragma: no cover - exercised in minimal envs + raise RuntimeError( + "tiktoken is required for token counting but is not installed." + " Install the optional 'tiktoken' extra to enable compression features." + ) from exc + _TIKTOKEN = tiktoken + return _TIKTOKEN + + +@lru_cache(maxsize=8) +def get_token_encoder(encoding_name: str = "o200k_base", optional: bool = False) -> Optional[Any]: + """Return a cached tiktoken encoder or ``None`` when optional.""" + + try: + module = _ensure_tiktoken() + except RuntimeError: + if optional: + return None + raise + return module.get_encoding(encoding_name) + + def generate_uuid() -> str: """Generate a UUID4 string.""" return str(uuid.uuid4()) def current_timestamp() -> datetime: """Get current UTC timestamp.""" - return datetime.utcnow() + + return datetime.now(timezone.utc) def hash_string(value: str) -> str: """Hash a string using SHA-256 and return the hex digest.""" @@ -21,13 +57,7 @@ def hash_dict(data: Any) -> str: return hash_string(str(data)) def num_tokens_from_string(string: str, encoding_name: str = "o200k_base") -> int: - """Returns the number of tokens in a text string. - Args: - string: The text string to count tokens for. - encoding_name: The name of the encoding to use. Defaults to "o200k_base". - Returns: - The number of tokens in the text string. - """ - encoding = tiktoken.get_encoding(encoding_name) - num_tokens = len(encoding.encode(string)) - return num_tokens \ No newline at end of file + """Return the number of tokens in ``string`` for ``encoding_name``.""" + + encoder = get_token_encoder(encoding_name, optional=False) + return len(encoder.encode(string)) diff --git a/meshmind/db/__init__.py b/meshmind/db/__init__.py index e69de29..e5c249e 100644 --- a/meshmind/db/__init__.py +++ b/meshmind/db/__init__.py @@ -0,0 +1,14 @@ +"""Graph driver implementations exposed for convenience.""" +from .base_driver import GraphDriver +from .in_memory_driver import InMemoryGraphDriver +from .memgraph_driver import MemgraphDriver +from .neo4j_driver import Neo4jGraphDriver +from .sqlite_driver import SQLiteGraphDriver + +__all__ = [ + "GraphDriver", + "InMemoryGraphDriver", + "MemgraphDriver", + "Neo4jGraphDriver", + "SQLiteGraphDriver", +] diff --git a/meshmind/db/base_driver.py b/meshmind/db/base_driver.py index 1e1366c..5fb6145 100644 --- a/meshmind/db/base_driver.py +++ b/meshmind/db/base_driver.py @@ -1,6 +1,8 @@ """Abstract base class for graph database drivers.""" +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional, Sequence import uuid @@ -22,7 +24,61 @@ def find(self, cypher: str, params: Dict[str, Any]) -> List[Dict[str, Any]]: """Execute a Cypher query and return results.""" raise NotImplementedError + @abstractmethod + def get_entity(self, uid: str) -> Optional[Dict[str, Any]]: + """Return a single entity by UUID, if it exists.""" + raise NotImplementedError + + @abstractmethod + def list_entities( + self, + namespace: Optional[str] = None, + entity_labels: Optional[Sequence[str]] = None, + *, + offset: int = 0, + limit: Optional[int] = None, + ) -> List[Dict[str, Any]]: + """Return entities, optionally filtered by namespace and label.""" + raise NotImplementedError + + def search_entities( + self, + query: Optional[str] = None, + namespace: Optional[str] = None, + entity_labels: Optional[Sequence[str]] = None, + *, + offset: int = 0, + limit: Optional[int] = None, + ) -> List[Dict[str, Any]]: + """Server-side memory search. Defaults to ``list_entities`` when unsupported.""" + + return self.list_entities( + namespace=namespace, + entity_labels=entity_labels, + offset=offset, + limit=limit, + ) + @abstractmethod def delete(self, uuid: uuid.UUID) -> None: """Delete a node or relationship by UUID.""" - raise NotImplementedError \ No newline at end of file + raise NotImplementedError + + @abstractmethod + def delete_triplet(self, subj: str, pred: str, obj: str) -> None: + """Delete a relationship identified by subject/predicate/object.""" + + raise NotImplementedError + + @abstractmethod + def list_triplets(self, namespace: Optional[str] = None) -> List[Dict[str, Any]]: + """Return stored triplets, optionally filtered by namespace.""" + + raise NotImplementedError + + def count_entities( + self, namespace: Optional[str] = None + ) -> Dict[str, Dict[str, int]]: + """Return memory counts grouped by namespace and entity label.""" + + raise NotImplementedError diff --git a/meshmind/db/factory.py b/meshmind/db/factory.py new file mode 100644 index 0000000..e7d18cd --- /dev/null +++ b/meshmind/db/factory.py @@ -0,0 +1,66 @@ +"""Factory helpers for constructing :class:`GraphDriver` instances.""" +from __future__ import annotations + +from typing import Callable, Dict, Type + +from meshmind.core.config import settings +from meshmind.db.base_driver import GraphDriver +from meshmind.db.in_memory_driver import InMemoryGraphDriver +from meshmind.db.memgraph_driver import MemgraphDriver +from meshmind.db.neo4j_driver import Neo4jGraphDriver +from meshmind.db.sqlite_driver import SQLiteGraphDriver + + +def _normalize_backend(name: str) -> str: + return name.replace("-", "_").lower() + + +def available_backends() -> Dict[str, Type[GraphDriver]]: + """Return the mapping of backend names to driver classes.""" + + return { + "memory": InMemoryGraphDriver, + "in_memory": InMemoryGraphDriver, + "inmemory": InMemoryGraphDriver, + "sqlite": SQLiteGraphDriver, + "memgraph": MemgraphDriver, + "neo4j": Neo4jGraphDriver, + } + + +def create_graph_driver(backend: str | None = None, **kwargs) -> GraphDriver: + """Instantiate a :class:`GraphDriver` for the requested backend.""" + + backend_name = _normalize_backend(backend or settings.GRAPH_BACKEND) + drivers = available_backends() + if backend_name not in drivers: + raise ValueError(f"Unsupported graph backend '{backend_name}'") + + driver_cls: Type[GraphDriver] = drivers[backend_name] + if driver_cls is InMemoryGraphDriver: + return driver_cls() + if driver_cls is SQLiteGraphDriver: + path = kwargs.get("path") or settings.SQLITE_PATH + return driver_cls(path) + if driver_cls is MemgraphDriver: + return driver_cls( + settings.MEMGRAPH_URI, + settings.MEMGRAPH_USERNAME, + settings.MEMGRAPH_PASSWORD, + ) + if driver_cls is Neo4jGraphDriver: + return driver_cls( + settings.NEO4J_URI, + settings.NEO4J_USERNAME, + settings.NEO4J_PASSWORD, + ) + return driver_cls(**kwargs) + + +def graph_driver_factory(backend: str | None = None, **kwargs) -> Callable[[], GraphDriver]: + """Return a callable that lazily constructs the configured driver.""" + + def _factory() -> GraphDriver: + return create_graph_driver(backend=backend, **kwargs) + + return _factory diff --git a/meshmind/db/in_memory_driver.py b/meshmind/db/in_memory_driver.py new file mode 100644 index 0000000..191758b --- /dev/null +++ b/meshmind/db/in_memory_driver.py @@ -0,0 +1,188 @@ +"""In-memory implementation of :class:`GraphDriver` for tests and local development.""" +from __future__ import annotations + +from typing import Any, Dict, List, Optional, Sequence, Tuple +from uuid import UUID, uuid4 + +from meshmind.db.base_driver import GraphDriver + + +class InMemoryGraphDriver(GraphDriver): + """A lightweight graph driver that stores entities and triplets in dictionaries.""" + + def __init__(self) -> None: + self._nodes: Dict[str, Dict[str, Any]] = {} + self._labels: Dict[str, str] = {} + self._triplets: Dict[Tuple[str, str, str], Dict[str, Any]] = {} + + # ------------------------------------------------------------------ + # Helpers + # ------------------------------------------------------------------ + def _ensure_uuid(self, props: Dict[str, Any]) -> str: + uid = props.get("uuid") + if isinstance(uid, UUID): + props["uuid"] = str(uid) + return str(uid) + if isinstance(uid, str): + return uid + new_uid = str(uuid4()) + props["uuid"] = new_uid + return new_uid + + # ------------------------------------------------------------------ + # GraphDriver API + # ------------------------------------------------------------------ + def upsert_entity(self, label: str, name: str, props: Dict[str, Any]) -> None: + props = dict(props) + uid = self._ensure_uuid(props) + props.setdefault("name", name) + props.setdefault("entity_label", label) + self._nodes[uid] = props + self._labels[uid] = label + + def upsert_edge(self, subj: str, pred: str, obj: str, props: Dict[str, Any]) -> None: + key = (str(subj), pred, str(obj)) + payload = dict(props) + payload.setdefault("subject", str(subj)) + payload.setdefault("predicate", pred) + payload.setdefault("object", str(obj)) + self._triplets[key] = payload + + def find(self, cypher: str, params: Dict[str, Any]) -> List[Dict[str, Any]]: + cypher = cypher.lower().strip() + if "where m.uuid" in cypher: + uid = str(params.get("uuid", "")) + node = self._nodes.get(uid) + if node is None: + return [] + return [{"m": dict(node)}] + if "where m.namespace" in cypher: + namespace = params.get("namespace") + results = [ + {"m": dict(node)} + for node in self._nodes.values() + if node.get("namespace") == namespace + ] + return results + if cypher.startswith("match (m) return m"): + return [{"m": dict(node)} for node in self._nodes.values()] + if cypher.startswith("match (a)-[r"): + namespace = params.get("namespace") + results: List[Dict[str, Any]] = [] + for payload in self._triplets.values(): + if namespace and payload.get("namespace") != namespace: + continue + record = { + "subject": payload.get("subject"), + "predicate": payload.get("predicate"), + "object": payload.get("object"), + "namespace": payload.get("namespace"), + "metadata": payload.get("metadata"), + "reference_time": payload.get("reference_time"), + } + results.append(record) + return results + return [] + + def get_entity(self, uid: str) -> Optional[Dict[str, Any]]: + node = self._nodes.get(str(uid)) + return dict(node) if node else None + + def _filtered_nodes( + self, + namespace: Optional[str], + entity_labels: Optional[Sequence[str]], + ) -> List[Dict[str, Any]]: + labels = set(entity_labels or []) + results: List[Dict[str, Any]] = [] + for node in self._nodes.values(): + if namespace is not None and node.get("namespace") != namespace: + continue + label = node.get("entity_label") or self._labels.get(str(node.get("uuid"))) + if labels and label not in labels: + continue + results.append(dict(node)) + return results + + def _slice(self, items: List[Dict[str, Any]], offset: int, limit: Optional[int]) -> List[Dict[str, Any]]: + offset = max(offset, 0) + if limit is None: + return items[offset:] + if limit <= 0: + return [] + return items[offset : offset + limit] + + def list_entities( + self, + namespace: Optional[str] = None, + entity_labels: Optional[Sequence[str]] = None, + *, + offset: int = 0, + limit: Optional[int] = None, + ) -> List[Dict[str, Any]]: + nodes = self._filtered_nodes(namespace, entity_labels) + return self._slice(nodes, offset, limit) + + def search_entities( + self, + query: Optional[str] = None, + namespace: Optional[str] = None, + entity_labels: Optional[Sequence[str]] = None, + *, + offset: int = 0, + limit: Optional[int] = None, + ) -> List[Dict[str, Any]]: + nodes = self._filtered_nodes(namespace, entity_labels) + if query: + needle = query.lower() + filtered: List[Dict[str, Any]] = [] + for node in nodes: + haystack = " ".join( + str(node.get(key, "")) for key in ("name", "content", "description") + ).lower() + metadata = node.get("metadata") or {} + if isinstance(metadata, dict): + haystack += " " + " ".join(str(value).lower() for value in metadata.values()) + if needle in haystack: + filtered.append(node) + nodes = filtered + return self._slice(nodes, offset, limit) + + def delete(self, uuid: UUID) -> None: + uid = str(uuid) + self._nodes.pop(uid, None) + self._labels.pop(uid, None) + to_delete = [key for key in self._triplets if key[0] == uid or key[2] == uid] + for key in to_delete: + self._triplets.pop(key, None) + + def delete_triplet(self, subj: str, pred: str, obj: str) -> None: + self._triplets.pop((str(subj), pred, str(obj)), None) + + def list_triplets(self, namespace: Optional[str] = None) -> List[Dict[str, Any]]: + results: List[Dict[str, Any]] = [] + for payload in self._triplets.values(): + if namespace and payload.get("namespace") != namespace: + continue + results.append( + { + "subject": payload.get("subject"), + "predicate": payload.get("predicate"), + "object": payload.get("object"), + "namespace": payload.get("namespace"), + "metadata": payload.get("metadata"), + "reference_time": payload.get("reference_time"), + } + ) + return results + + def count_entities(self, namespace: Optional[str] = None) -> Dict[str, Dict[str, int]]: + counts: Dict[str, Dict[str, int]] = {} + for node in self._nodes.values(): + ns = node.get("namespace") + if namespace is not None and ns != namespace: + continue + label = node.get("entity_label") or self._labels.get(str(node.get("uuid"))) or "Unknown" + bucket = counts.setdefault(ns or "default", {}) + bucket[label] = bucket.get(label, 0) + 1 + return counts diff --git a/meshmind/db/memgraph_driver.py b/meshmind/db/memgraph_driver.py index f4e8c4e..a8d79b0 100644 --- a/meshmind/db/memgraph_driver.py +++ b/meshmind/db/memgraph_driver.py @@ -1,110 +1,255 @@ -"""Memgraph implementation of GraphDriver.""" -from typing import Any, Dict, List -from .base_driver import GraphDriver +"""Memgraph implementation of :class:`GraphDriver` using ``mgclient``.""" +from __future__ import annotations - -"""Memgraph implementation of GraphDriver using mgclient.""" -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Sequence from urllib.parse import urlparse -try: +from meshmind.db.base_driver import GraphDriver + +try: # pragma: no cover - optional dependency import mgclient -except ImportError: +except ImportError: # pragma: no cover - optional dependency mgclient = None # type: ignore -from .base_driver import GraphDriver - class MemgraphDriver(GraphDriver): - """Memgraph driver implementation of GraphDriver using mgclient.""" + """Memgraph driver implementation backed by ``mgclient``.""" - def __init__(self, uri: str, username: str = None, password: str = None) -> None: - """Initialize Memgraph driver with Bolt URI and credentials.""" + def __init__(self, uri: str, username: str = "", password: str = "") -> None: if mgclient is None: raise ImportError("mgclient is required for MemgraphDriver") + self.uri = uri self.username = username self.password = password - # Parse URI: bolt://host:port + parsed = urlparse(uri) - host = parsed.hostname or 'localhost' + host = parsed.hostname or "localhost" port = parsed.port or 7687 - # Establish connection - self._conn = mgclient.connect( + + self._conn = mgclient.connect( # type: ignore[union-attr] host=host, port=port, - username=username, - password=password, + username=username or None, + password=password or None, ) self._cursor = self._conn.cursor() - def _execute(self, cypher: str, params: Optional[Dict[str, Any]] = None): - if params is None: - params = {} + # ------------------------------------------------------------------ + # Internal helpers + # ------------------------------------------------------------------ + def _execute(self, cypher: str, params: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]: + params = params or {} self._cursor.execute(cypher, params) try: rows = self._cursor.fetchall() cols = [col[0] for col in self._cursor.description] - results: List[Dict[str, Any]] = [] - for row in rows: - rec: Dict[str, Any] = {} - for idx, val in enumerate(row): - rec[cols[idx]] = val - results.append(rec) - return results except Exception: return [] + results: List[Dict[str, Any]] = [] + for row in rows: + record: Dict[str, Any] = {} + for idx, value in enumerate(row): + record[cols[idx]] = value + results.append(record) + return results + + @staticmethod + def _sanitize_predicate(predicate: str) -> str: + return predicate.replace("`", "") + + @staticmethod + def _normalize_node(node: Any) -> Dict[str, Any]: + if hasattr(node, "properties"): + try: + return dict(node.properties) # type: ignore[attr-defined] + except Exception: + pass + if isinstance(node, dict): + return dict(node) + if hasattr(node, "_properties"): + return dict(getattr(node, "_properties")) + return {k: v for k, v in getattr(node, "__dict__", {}).items() if not k.startswith("_")} + + # ------------------------------------------------------------------ + # GraphDriver API + # ------------------------------------------------------------------ def upsert_entity(self, label: str, name: str, props: Dict[str, Any]) -> None: - """Insert or update an entity node by uuid.""" - uid = props.get('uuid') + uid = props.get("uuid") cypher = ( f"MERGE (n:{label} {{uuid: $uuid}})\n" f"SET n += $props" ) - params = {'uuid': str(uid), 'props': props} + params = {"uuid": str(uid), "props": props} self._execute(cypher, params) self._conn.commit() def upsert_edge(self, subj: str, pred: str, obj: str, props: Dict[str, Any]) -> None: - """Insert or update an edge between two entities identified by uuid.""" + predicate = self._sanitize_predicate(pred) cypher = ( - f"MATCH (a {{uuid: $subj}}), (b {{uuid: $obj}})\n" - f"MERGE (a)-[r:`{pred}`]->(b)\n" - f"SET r += $props" + "MATCH (a {uuid: $subj}), (b {uuid: $obj})\n" + f"MERGE (a)-[r:`{predicate}`]->(b)\n" + "SET r += $props" ) - params = {'subj': str(subj), 'obj': str(obj), 'props': props} + params = {"subj": str(subj), "obj": str(obj), "props": props} self._execute(cypher, params) self._conn.commit() def find(self, cypher: str, params: Dict[str, Any]) -> List[Dict[str, Any]]: - """Execute a Cypher query and return results as list of dicts.""" return self._execute(cypher, params) + def get_entity(self, uid: str) -> Optional[Dict[str, Any]]: + records = self.find( + "MATCH (m) WHERE m.uuid = $uuid RETURN m", + {"uuid": str(uid)}, + ) + if not records: + return None + node = records[0].get("m", records[0]) + return self._normalize_node(node) + + def list_entities( + self, + namespace: Optional[str] = None, + entity_labels: Optional[Sequence[str]] = None, + *, + offset: int = 0, + limit: Optional[int] = None, + ) -> List[Dict[str, Any]]: + if limit is not None and limit <= 0: + return [] + clauses = ["($namespace IS NULL OR m.namespace = $namespace)"] + clauses.append("($labels IS NULL OR m.entity_label IN $labels)") + cypher = ["MATCH (m)"] + cypher.append("WHERE " + " AND ".join(clauses)) + cypher.append("RETURN m") + cypher.append("ORDER BY m.namespace, m.entity_label, m.name") + params = { + "namespace": namespace, + "labels": list(entity_labels) if entity_labels else None, + "offset": max(offset, 0), + } + cypher.append("SKIP $offset") + if limit is not None: + cypher.append("LIMIT $limit") + params["limit"] = limit + records = self.find("\n".join(cypher), params) + entities: List[Dict[str, Any]] = [] + for record in records: + node = record.get("m", record) + entities.append(self._normalize_node(node)) + return entities + + def search_entities( + self, + query: Optional[str] = None, + namespace: Optional[str] = None, + entity_labels: Optional[Sequence[str]] = None, + *, + offset: int = 0, + limit: Optional[int] = None, + ) -> List[Dict[str, Any]]: + if limit is not None and limit <= 0: + return [] + clauses = ["($namespace IS NULL OR m.namespace = $namespace)"] + clauses.append("($labels IS NULL OR m.entity_label IN $labels)") + params = { + "namespace": namespace, + "labels": list(entity_labels) if entity_labels else None, + "offset": max(offset, 0), + "search": query.lower() if query else None, + } + text_clause = ( + "($search IS NULL OR " + "toLower(coalesce(m.name, '')) CONTAINS $search OR " + "toLower(coalesce(m.content, '')) CONTAINS $search OR " + "toLower(coalesce(m.description, '')) CONTAINS $search OR " + "($search IS NOT NULL AND exists(m.metadata) AND " + "any(value IN values(m.metadata) WHERE toLower(toString(value)) CONTAINS $search))" + ")" + ) + clauses.append(text_clause) + cypher = ["MATCH (m)"] + cypher.append("WHERE " + " AND ".join(clauses)) + cypher.append("RETURN m") + cypher.append("ORDER BY m.reference_time DESC, m.name") + cypher.append("SKIP $offset") + if limit is not None: + cypher.append("LIMIT $limit") + params["limit"] = limit + records = self.find("\n".join(cypher), params) + entities: List[Dict[str, Any]] = [] + for record in records: + node = record.get("m", record) + entities.append(self._normalize_node(node)) + return entities + def delete(self, uuid: Any) -> None: - """Delete a node (and detach relationships) by uuid.""" cypher = "MATCH (n {uuid: $uuid}) DETACH DELETE n" - params = {'uuid': str(uuid)} + params = {"uuid": str(uuid)} + self._execute(cypher, params) + self._conn.commit() + + def delete_triplet(self, subj: str, pred: str, obj: str) -> None: + predicate = self._sanitize_predicate(pred) + cypher = ( + "MATCH (a {uuid: $subj})-[r:`{predicate}`]->(b {uuid: $obj}) " + "DELETE r" + ) + params = {"subj": str(subj), "obj": str(obj)} self._execute(cypher, params) self._conn.commit() + def list_triplets(self, namespace: Optional[str] = None) -> List[Dict[str, Any]]: + cypher = ( + "MATCH (a)-[r]->(b)\n" + "WHERE $namespace IS NULL OR r.namespace = $namespace\n" + "RETURN a.uuid AS subject, type(r) AS predicate, b.uuid AS object, " + "r.namespace AS namespace, r.metadata AS metadata, r.reference_time AS reference_time" + ) + params = {"namespace": namespace} + return self._execute(cypher, params) + + def count_entities(self, namespace: Optional[str] = None) -> Dict[str, Dict[str, int]]: + clauses = ["($namespace IS NULL OR m.namespace = $namespace)"] + cypher = ( + "MATCH (m)\n" + "WHERE " + + " AND ".join(clauses) + + "\nRETURN coalesce(m.namespace, '') AS namespace, " + "m.entity_label AS label, count(m) AS count" + ) + params = {"namespace": namespace} + rows = self.find(cypher, params) + results: Dict[str, Dict[str, int]] = {} + for row in rows: + ns = row.get("namespace") or "default" + label = row.get("label") or "Unknown" + count = int(row.get("count", 0)) + bucket = results.setdefault(ns, {}) + bucket[label] = count + return results + + # ------------------------------------------------------------------ + # Convenience helpers + # ------------------------------------------------------------------ def vector_search(self, embedding: List[float], top_k: int = 10) -> List[Dict[str, Any]]: - """ - Fallback vector search: loads all embeddings and ranks by cosine similarity. - """ from meshmind.core.similarity import cosine_similarity - # Load all entities with embeddings - records = self.find("MATCH (n) WHERE exists(n.embedding) RETURN n.embedding AS emb, n AS node", {}) + + records = self.find( + "MATCH (n) WHERE exists(n.embedding) RETURN n.embedding AS emb, n AS node", + {}, + ) scored = [] for rec in records: - emb = rec.get('emb') + emb = rec.get("emb") if not isinstance(emb, list): continue try: score = cosine_similarity(embedding, emb) except Exception: score = 0.0 - scored.append({'node': rec.get('node'), 'score': float(score)}) - # Sort and take top_k - scored.sort(key=lambda x: x['score'], reverse=True) - return scored[:top_k] \ No newline at end of file + scored.append({"node": rec.get("node"), "score": float(score)}) + scored.sort(key=lambda item: item["score"], reverse=True) + return scored[:top_k] diff --git a/meshmind/db/neo4j_driver.py b/meshmind/db/neo4j_driver.py new file mode 100644 index 0000000..cfd2214 --- /dev/null +++ b/meshmind/db/neo4j_driver.py @@ -0,0 +1,200 @@ +"""Neo4j implementation of :class:`GraphDriver` using the official driver.""" +from __future__ import annotations + +from typing import Any, Dict, List, Optional, Sequence + +from meshmind.db.base_driver import GraphDriver + +try: # pragma: no cover - optional dependency + from neo4j import GraphDatabase # type: ignore +except ImportError: # pragma: no cover - optional dependency + GraphDatabase = None # type: ignore + + +class Neo4jGraphDriver(GraphDriver): + """GraphDriver backed by Neo4j via the ``neo4j`` Python driver.""" + + def __init__(self, uri: str, username: str = "neo4j", password: str = "") -> None: + if GraphDatabase is None: + raise ImportError("neo4j driver is required for Neo4jGraphDriver") + auth = None + if username or password: + auth = (username or None, password or None) + self._driver = GraphDatabase.driver(uri, auth=auth) + + # ------------------------------------------------------------------ + # Internal helpers + # ------------------------------------------------------------------ + def _run(self, cypher: str, params: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]: + params = params or {} + with self._driver.session() as session: # type: ignore[attr-defined] + result = session.run(cypher, **params) + records = [] + for record in result: + records.append(record.data()) + return records + + @staticmethod + def _normalize_node(node: Any) -> Dict[str, Any]: + if hasattr(node, "_properties"): + return dict(node._properties) # type: ignore[attr-defined] + if isinstance(node, dict): + return dict(node) + return {k: v for k, v in getattr(node, "__dict__", {}).items() if not k.startswith("_")} + + # ------------------------------------------------------------------ + # GraphDriver API + # ------------------------------------------------------------------ + def upsert_entity(self, label: str, name: str, props: Dict[str, Any]) -> None: + cypher = ( + f"MERGE (n:{label} {{uuid: $uuid}})\n" + "SET n += $props" + ) + params = {"uuid": str(props.get("uuid")), "props": props} + self._run(cypher, params) + + def upsert_edge(self, subj: str, pred: str, obj: str, props: Dict[str, Any]) -> None: + predicate = pred.replace("`", "") + cypher = ( + "MATCH (a {uuid: $subj}), (b {uuid: $obj})\n" + f"MERGE (a)-[r:`{predicate}`]->(b)\n" + "SET r += $props" + ) + params = {"subj": str(subj), "obj": str(obj), "props": props} + self._run(cypher, params) + + def find(self, cypher: str, params: Dict[str, Any]) -> List[Dict[str, Any]]: + return self._run(cypher, params) + + def get_entity(self, uid: str) -> Optional[Dict[str, Any]]: + records = self.find("MATCH (m) WHERE m.uuid = $uuid RETURN m", {"uuid": str(uid)}) + if not records: + return None + node = records[0].get("m", records[0]) + return self._normalize_node(node) + + def list_entities( + self, + namespace: Optional[str] = None, + entity_labels: Optional[Sequence[str]] = None, + *, + offset: int = 0, + limit: Optional[int] = None, + ) -> List[Dict[str, Any]]: + if limit is not None and limit <= 0: + return [] + clauses = ["($namespace IS NULL OR m.namespace = $namespace)"] + clauses.append("($labels IS NULL OR m.entity_label IN $labels)") + cypher = ["MATCH (m)"] + cypher.append("WHERE " + " AND ".join(clauses)) + cypher.append("RETURN m") + cypher.append("ORDER BY m.namespace, m.entity_label, m.name") + cypher.append("SKIP $offset") + if limit is not None: + cypher.append("LIMIT $limit") + params = { + "namespace": namespace, + "labels": list(entity_labels) if entity_labels else None, + "offset": max(offset, 0), + } + if limit is not None: + params["limit"] = limit + records = self.find("\n".join(cypher), params) + return [self._normalize_node(rec.get("m", rec)) for rec in records] + + def search_entities( + self, + query: Optional[str] = None, + namespace: Optional[str] = None, + entity_labels: Optional[Sequence[str]] = None, + *, + offset: int = 0, + limit: Optional[int] = None, + ) -> List[Dict[str, Any]]: + if limit is not None and limit <= 0: + return [] + clauses = ["($namespace IS NULL OR m.namespace = $namespace)"] + clauses.append("($labels IS NULL OR m.entity_label IN $labels)") + params = { + "namespace": namespace, + "labels": list(entity_labels) if entity_labels else None, + "offset": max(offset, 0), + "search": query.lower() if query else None, + } + text_clause = ( + "($search IS NULL OR " + "toLower(coalesce(m.name, '')) CONTAINS $search OR " + "toLower(coalesce(m.content, '')) CONTAINS $search OR " + "toLower(coalesce(m.description, '')) CONTAINS $search OR " + "($search IS NOT NULL AND exists(m.metadata) AND " + "any(value IN values(m.metadata) WHERE toLower(toString(value)) CONTAINS $search))" + ")" + ) + clauses.append(text_clause) + cypher = ["MATCH (m)"] + cypher.append("WHERE " + " AND ".join(clauses)) + cypher.append("RETURN m") + cypher.append("ORDER BY m.reference_time DESC, m.name") + cypher.append("SKIP $offset") + if limit is not None: + cypher.append("LIMIT $limit") + params["limit"] = limit + records = self.find("\n".join(cypher), params) + return [self._normalize_node(rec.get("m", rec)) for rec in records] + + def delete(self, uuid: Any) -> None: + self._run("MATCH (m {uuid: $uuid}) DETACH DELETE m", {"uuid": str(uuid)}) + + def delete_triplet(self, subj: str, pred: str, obj: str) -> None: + predicate = pred.replace("`", "") + cypher = ( + f"MATCH (a {{uuid: $subj}})-[r:`{predicate}`]->(b {{uuid: $obj}})" + " DELETE r" + ) + self._run(cypher, {"subj": str(subj), "obj": str(obj)}) + + def list_triplets(self, namespace: Optional[str] = None) -> List[Dict[str, Any]]: + cypher = ( + "MATCH (a)-[r]->(b)\n" + "WHERE $namespace IS NULL OR r.namespace = $namespace\n" + "RETURN a.uuid AS subject, type(r) AS predicate, b.uuid AS object, " + "r.namespace AS namespace, r.metadata AS metadata, r.reference_time AS reference_time" + ) + params = {"namespace": namespace} + return self.find(cypher, params) + + def close(self) -> None: + self._driver.close() + + def count_entities(self, namespace: Optional[str] = None) -> Dict[str, Dict[str, int]]: + clauses = ["($namespace IS NULL OR m.namespace = $namespace)"] + cypher = ( + "MATCH (m)\n" + "WHERE " + + " AND ".join(clauses) + + "\nRETURN coalesce(m.namespace, '') AS namespace, " + "m.entity_label AS label, count(m) AS count" + ) + params = {"namespace": namespace} + rows = self.find(cypher, params) + results: Dict[str, Dict[str, int]] = {} + for row in rows: + ns = row.get("namespace") or "default" + label = row.get("label") or "Unknown" + count = int(row.get("count", 0)) + bucket = results.setdefault(ns, {}) + bucket[label] = count + return results + + def verify_connectivity(self) -> bool: + """Use the Neo4j driver to verify connectivity.""" + + checker = getattr(self._driver, "verify_connectivity", None) + if callable(checker): + checker() + return True + try: + self._run("RETURN 1 AS ok") + except Exception: + return False + return True diff --git a/meshmind/db/sqlite_driver.py b/meshmind/db/sqlite_driver.py new file mode 100644 index 0000000..24bb468 --- /dev/null +++ b/meshmind/db/sqlite_driver.py @@ -0,0 +1,308 @@ +"""SQLite implementation of :class:`GraphDriver` for lightweight persistence.""" +from __future__ import annotations + +import json +import sqlite3 +from pathlib import Path +from typing import Any, Dict, List, Optional, Sequence + +from meshmind.db.base_driver import GraphDriver + + +class SQLiteGraphDriver(GraphDriver): + """GraphDriver backed by SQLite tables using simple JSON columns.""" + + def __init__(self, path: str | Path = ":memory:") -> None: + self._path = str(path) + self._conn = sqlite3.connect(self._path) + self._conn.row_factory = sqlite3.Row + self._ensure_schema() + + # ------------------------------------------------------------------ + # Internal helpers + # ------------------------------------------------------------------ + def _ensure_schema(self) -> None: + cur = self._conn.cursor() + cur.execute( + """ + CREATE TABLE IF NOT EXISTS entities ( + uuid TEXT PRIMARY KEY, + label TEXT NOT NULL, + name TEXT NOT NULL, + namespace TEXT, + props TEXT NOT NULL + ) + """ + ) + cur.execute( + """ + CREATE TABLE IF NOT EXISTS triplets ( + subject TEXT NOT NULL, + predicate TEXT NOT NULL, + object TEXT NOT NULL, + namespace TEXT, + metadata TEXT, + reference_time TEXT, + PRIMARY KEY (subject, predicate, object) + ) + """ + ) + self._conn.commit() + + def _row_to_dict(self, row: sqlite3.Row) -> Dict[str, Any]: + payload = dict(row) + if "props" in payload and payload["props"]: + props = payload.pop("props") + if isinstance(props, str): + payload.update(json.loads(props)) + elif isinstance(props, dict): + payload.update(props) + if "metadata" in payload and isinstance(payload["metadata"], str): + payload["metadata"] = json.loads(payload["metadata"]) + return payload + + # ------------------------------------------------------------------ + # GraphDriver API + # ------------------------------------------------------------------ + def upsert_entity(self, label: str, name: str, props: Dict[str, Any]) -> None: + payload = dict(props) + uid = str(payload.get("uuid")) + if not uid: + raise ValueError("Memory props must include a UUID for SQLiteGraphDriver") + payload.setdefault("entity_label", label) + payload.setdefault("name", name) + namespace = payload.get("namespace") + cur = self._conn.cursor() + cur.execute( + """ + INSERT INTO entities (uuid, label, name, namespace, props) + VALUES (:uuid, :label, :name, :namespace, :props) + ON CONFLICT(uuid) DO UPDATE SET + label=excluded.label, + name=excluded.name, + namespace=excluded.namespace, + props=excluded.props + """, + { + "uuid": uid, + "label": payload.get("entity_label", label), + "name": payload.get("name", name), + "namespace": namespace, + "props": json.dumps(payload), + }, + ) + self._conn.commit() + + def upsert_edge(self, subj: str, pred: str, obj: str, props: Dict[str, Any]) -> None: + payload = dict(props) + payload.setdefault("subject", subj) + payload.setdefault("predicate", pred) + payload.setdefault("object", obj) + metadata = payload.get("metadata") or {} + cur = self._conn.cursor() + cur.execute( + """ + INSERT INTO triplets (subject, predicate, object, namespace, metadata, reference_time) + VALUES (:subject, :predicate, :object, :namespace, :metadata, :reference_time) + ON CONFLICT(subject, predicate, object) DO UPDATE SET + namespace=excluded.namespace, + metadata=excluded.metadata, + reference_time=excluded.reference_time + """, + { + "subject": payload["subject"], + "predicate": payload["predicate"], + "object": payload["object"], + "namespace": payload.get("namespace"), + "metadata": json.dumps(metadata), + "reference_time": payload.get("reference_time"), + }, + ) + self._conn.commit() + + def find(self, cypher: str, params: Dict[str, Any]) -> List[Dict[str, Any]]: + # Provide compatibility for simple MATCH queries used by MemoryManager. + cypher_lower = cypher.lower().strip() + cur = self._conn.cursor() + if "where m.uuid" in cypher_lower: + cur.execute("SELECT * FROM entities WHERE uuid = :uuid", {"uuid": params.get("uuid")}) + row = cur.fetchone() + if not row: + return [] + return [{"m": self._row_to_dict(row)}] + if "where m.namespace" in cypher_lower: + cur.execute( + "SELECT * FROM entities WHERE namespace = :namespace", + {"namespace": params.get("namespace")}, + ) + rows = cur.fetchall() + return [{"m": self._row_to_dict(row)} for row in rows] + if cypher_lower.startswith("match (m) return m"): + cur.execute("SELECT * FROM entities") + rows = cur.fetchall() + return [{"m": self._row_to_dict(row)} for row in rows] + if cypher_lower.startswith("match (a)-[r"): + namespace = params.get("namespace") + if namespace: + cur.execute( + "SELECT * FROM triplets WHERE namespace = :namespace", + {"namespace": namespace}, + ) + else: + cur.execute("SELECT * FROM triplets") + rows = cur.fetchall() + return [dict(row) for row in rows] + return [] + + def get_entity(self, uid: str) -> Optional[Dict[str, Any]]: + cur = self._conn.cursor() + cur.execute("SELECT * FROM entities WHERE uuid = :uuid", {"uuid": uid}) + row = cur.fetchone() + return self._row_to_dict(row) if row else None + + def list_entities( + self, + namespace: Optional[str] = None, + entity_labels: Optional[Sequence[str]] = None, + *, + offset: int = 0, + limit: Optional[int] = None, + ) -> List[Dict[str, Any]]: + cur = self._conn.cursor() + clauses: List[str] = [] + params: Dict[str, Any] = {} + if namespace: + clauses.append("namespace = :namespace") + params["namespace"] = namespace + if entity_labels: + placeholders = [] + for idx, label in enumerate(entity_labels): + key = f"label_{idx}" + placeholders.append(f":{key}") + params[key] = label + clauses.append(f"label IN ({', '.join(placeholders)})") + query = "SELECT * FROM entities" + if clauses: + query += " WHERE " + " AND ".join(clauses) + query += " ORDER BY namespace, label, name" + if limit is not None: + if limit <= 0: + return [] + query += " LIMIT :limit" + params["limit"] = limit + if offset > 0: + query += " OFFSET :offset" + params["offset"] = offset + elif offset > 0: + query += " LIMIT -1 OFFSET :offset" + params["offset"] = offset + cur.execute(query, params) + rows = cur.fetchall() + return [self._row_to_dict(row) for row in rows] + + def search_entities( + self, + query: Optional[str] = None, + namespace: Optional[str] = None, + entity_labels: Optional[Sequence[str]] = None, + *, + offset: int = 0, + limit: Optional[int] = None, + ) -> List[Dict[str, Any]]: + cur = self._conn.cursor() + clauses: List[str] = [] + params: Dict[str, Any] = {} + if namespace: + clauses.append("namespace = :namespace") + params["namespace"] = namespace + if entity_labels: + placeholders = [] + for idx, label in enumerate(entity_labels): + key = f"label_{idx}" + placeholders.append(f":{key}") + params[key] = label + clauses.append(f"label IN ({', '.join(placeholders)})") + if query: + params["needle"] = f"%{query.lower()}%" + clauses.append( + "(LOWER(name) LIKE :needle OR LOWER(props) LIKE :needle)" + ) + sql = "SELECT * FROM entities" + if clauses: + sql += " WHERE " + " AND ".join(clauses) + sql += " ORDER BY namespace, label, name" + if limit is not None: + if limit <= 0: + return [] + sql += " LIMIT :limit" + params["limit"] = limit + if offset > 0: + sql += " OFFSET :offset" + params["offset"] = offset + elif offset > 0: + sql += " LIMIT -1 OFFSET :offset" + params["offset"] = offset + cur.execute(sql, params) + rows = cur.fetchall() + return [self._row_to_dict(row) for row in rows] + + def delete(self, uuid: Any) -> None: + cur = self._conn.cursor() + cur.execute("DELETE FROM entities WHERE uuid = :uuid", {"uuid": str(uuid)}) + cur.execute( + "DELETE FROM triplets WHERE subject = :uuid OR object = :uuid", + {"uuid": str(uuid)}, + ) + self._conn.commit() + + def delete_triplet(self, subj: str, pred: str, obj: str) -> None: + cur = self._conn.cursor() + cur.execute( + "DELETE FROM triplets WHERE subject = :subject AND predicate = :predicate AND object = :object", + {"subject": str(subj), "predicate": pred, "object": str(obj)}, + ) + self._conn.commit() + + def list_triplets(self, namespace: Optional[str] = None) -> List[Dict[str, Any]]: + cur = self._conn.cursor() + if namespace: + cur.execute( + "SELECT * FROM triplets WHERE namespace = :namespace", + {"namespace": namespace}, + ) + else: + cur.execute("SELECT * FROM triplets") + rows = cur.fetchall() + result = [] + for row in rows: + payload = dict(row) + metadata = payload.get("metadata") + payload["metadata"] = json.loads(metadata) if metadata else {} + result.append(payload) + return result + + def count_entities(self, namespace: Optional[str] = None) -> Dict[str, Dict[str, int]]: + cur = self._conn.cursor() + params: Dict[str, Any] = {} + sql = ( + "SELECT COALESCE(namespace, '') AS namespace, label, COUNT(*) AS count " + "FROM entities" + ) + if namespace: + sql += " WHERE namespace = :namespace" + params["namespace"] = namespace + sql += " GROUP BY namespace, label" + cur.execute(sql, params) + rows = cur.fetchall() + results: Dict[str, Dict[str, int]] = {} + for row in rows: + payload = dict(row) + ns = payload.get("namespace") or "default" + label = payload.get("label") or "Unknown" + count = int(payload.get("count", 0)) + bucket = results.setdefault(ns, {}) + bucket[label] = count + return results + + def close(self) -> None: + self._conn.close() diff --git a/meshmind/llm_client.py b/meshmind/llm_client.py new file mode 100644 index 0000000..7208f02 --- /dev/null +++ b/meshmind/llm_client.py @@ -0,0 +1,259 @@ +"""Provider-agnostic wrapper around OpenAI-compatible SDKs.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Dict, Optional + +DEFAULT_MODEL = "gpt-5-nano" + +try: # pragma: no cover - optional dependency import + from openai import OpenAI as _OpenAI + from openai import RateLimitError as _RateLimitError +except ImportError: # pragma: no cover - exercised in tests without openai + _OpenAI = None # type: ignore[assignment] + + class _RateLimitError(Exception): + """Fallback error used when the OpenAI SDK is unavailable.""" + + pass + + +RateLimitError = _RateLimitError + + +@dataclass(frozen=True) +class LLMConfig: + """Configuration for an OpenAI-compatible client.""" + + api_key: Optional[str] = None + base_urls: Dict[str, Optional[str]] = field( + default_factory=lambda: {"default": None} + ) + models: Dict[str, str] = field(default_factory=lambda: {"default": DEFAULT_MODEL}) + default_model: str = DEFAULT_MODEL + + def model_for(self, operation: str, fallback: Optional[str] = None) -> str: + """Return the preferred model for a given operation.""" + + return ( + self.models.get(operation) + or self.models.get("default") + or fallback + or self.default_model + ) + + def base_url_for(self, operation: str) -> Optional[str]: + """Return the preferred base URL for a given operation.""" + + base_url = self.base_urls.get(operation) + if base_url: + return base_url + return self.base_urls.get("default") or None + + def override( + self, + *, + models: Optional[Dict[str, Optional[str]]] = None, + base_urls: Optional[Dict[str, Optional[str]]] = None, + api_key: Optional[str] = None, + ) -> "LLMConfig": + """Return a copy of the config with overrides applied.""" + + new_models = dict(self.models) + if models: + for key, value in models.items(): + if value: + new_models[key] = value + new_base_urls = dict(self.base_urls) + if base_urls: + for key, value in base_urls.items(): + if value is not None: + new_base_urls[key] = value or None + new_api_key = api_key if api_key not in (None, "") else self.api_key + return LLMConfig( + api_key=new_api_key, + base_urls=new_base_urls, + models=new_models, + default_model=self.default_model, + ) + + +class _ResponsesProxy: + def __init__(self, parent: "LLMClient") -> None: + self._parent = parent + + def create( + self, + *, + operation: Optional[str] = None, + model: Optional[str] = None, + base_url: Optional[str] = None, + **kwargs: Any, + ) -> Any: + return self._parent.responses_create( + operation=operation or "extraction", + model=model, + base_url=base_url, + **kwargs, + ) + + +class _EmbeddingsProxy: + def __init__(self, parent: "LLMClient") -> None: + self._parent = parent + + def create( + self, + *, + operation: Optional[str] = None, + model: Optional[str] = None, + base_url: Optional[str] = None, + **kwargs: Any, + ) -> Any: + return self._parent.embeddings_create( + operation=operation or "embedding", + model=model, + base_url=base_url, + **kwargs, + ) + + +class LLMClient: + """Thin wrapper around the OpenAI SDK that centralises configuration.""" + + def __init__( + self, + config: LLMConfig, + *, + client_kwargs: Optional[Dict[str, Any]] = None, + ) -> None: + if _OpenAI is None: + raise ImportError( + "openai package is required to construct an LLM client." + ) + + self.config = config + base_kwargs = dict(client_kwargs or {}) + if config.api_key: + base_kwargs.setdefault("api_key", config.api_key) + default_base = config.base_url_for("default") + if default_base: + base_kwargs.setdefault("base_url", default_base) + self._default_kwargs = base_kwargs + self._client_cache: Dict[Optional[str], Any] = {} + + self.responses = _ResponsesProxy(self) + self.embeddings = _EmbeddingsProxy(self) + + def _client_for(self, base_url: Optional[str]) -> Any: + key = base_url or self._default_kwargs.get("base_url") or "__default__" + if key not in self._client_cache: + kwargs = dict(self._default_kwargs) + if base_url: + kwargs["base_url"] = base_url + self._client_cache[key] = _OpenAI(**kwargs) + return self._client_cache[key] + + def responses_create( + self, + *, + operation: str = "extraction", + model: Optional[str] = None, + base_url: Optional[str] = None, + **kwargs: Any, + ) -> Any: + resolved_model = model or self.config.model_for(operation) + resolved_base = base_url if base_url not in ("", None) else None + if resolved_base is None: + resolved_base = self.config.base_url_for(operation) + client = self._client_for(resolved_base) + kwargs.setdefault("model", resolved_model) + return client.responses.create(**kwargs) + + def embeddings_create( + self, + *, + operation: str = "embedding", + model: Optional[str] = None, + base_url: Optional[str] = None, + **kwargs: Any, + ) -> Any: + resolved_model = model or self.config.model_for(operation) + resolved_base = base_url if base_url not in ("", None) else None + if resolved_base is None: + resolved_base = self.config.base_url_for(operation) + client = self._client_for(resolved_base) + kwargs.setdefault("model", resolved_model) + return client.embeddings.create(**kwargs) + + def with_overrides( + self, + *, + models: Optional[Dict[str, Optional[str]]] = None, + base_urls: Optional[Dict[str, Optional[str]]] = None, + api_key: Optional[str] = None, + ) -> "LLMClient": + """Create a new client that applies the provided overrides.""" + + return LLMClient( + self.config.override( + models=models, base_urls=base_urls, api_key=api_key + ), + client_kwargs=self._default_kwargs, + ) + + +def build_llm_config_from_settings(settings: Any) -> LLMConfig: + """Construct an :class:`LLMConfig` instance from settings.""" + + base_urls = { + "default": settings.LLM_DEFAULT_BASE_URL or None, + "extraction": settings.LLM_EXTRACTION_BASE_URL or None, + "embedding": settings.LLM_EMBEDDING_BASE_URL or None, + "rerank": settings.LLM_RERANK_BASE_URL or None, + } + models = { + "default": settings.LLM_DEFAULT_MODEL or DEFAULT_MODEL, + "extraction": settings.LLM_EXTRACTION_MODEL or None, + "embedding": settings.LLM_EMBEDDING_MODEL, + "rerank": settings.LLM_RERANK_MODEL or None, + } + cleaned_base_urls = { + key: value for key, value in base_urls.items() if value is not None + } + cleaned_models = { + key: value for key, value in models.items() if value + } + api_key = settings.LLM_API_KEY or settings.OPENAI_API_KEY or None + return LLMConfig( + api_key=api_key, + base_urls={**{"default": cleaned_base_urls.get("default")}, **cleaned_base_urls}, + models={**{"default": cleaned_models.get("default", DEFAULT_MODEL)}, **cleaned_models}, + default_model=cleaned_models.get("default", DEFAULT_MODEL), + ) + + +def build_default_llm_client( + settings: Any, + *, + models: Optional[Dict[str, Optional[str]]] = None, + base_urls: Optional[Dict[str, Optional[str]]] = None, + api_key: Optional[str] = None, + client_kwargs: Optional[Dict[str, Any]] = None, +) -> LLMClient: + """Helper to instantiate an :class:`LLMClient` using application settings.""" + + config = build_llm_config_from_settings(settings) + if any((models, base_urls, api_key)): + config = config.override(models=models, base_urls=base_urls, api_key=api_key) + return LLMClient(config, client_kwargs=client_kwargs) + + +__all__ = [ + "DEFAULT_MODEL", + "LLMClient", + "LLMConfig", + "RateLimitError", + "build_default_llm_client", + "build_llm_config_from_settings", +] diff --git a/meshmind/models/registry.py b/meshmind/models/registry.py index bd98f87..e4f3028 100644 --- a/meshmind/models/registry.py +++ b/meshmind/models/registry.py @@ -30,4 +30,25 @@ def add(cls, label: str) -> None: @classmethod def allowed(cls, label: str) -> bool: """Check if a predicate label is allowed.""" - return label in cls._predicates \ No newline at end of file + return label in cls._predicates + + @classmethod + def all(cls) -> Set[str]: + """Return all registered predicate labels.""" + + return set(cls._predicates) + + @classmethod + def clear(cls) -> None: + """Remove all registered predicates (testing helper).""" + + cls._predicates.clear() + + @classmethod + def remove(cls, label: str) -> bool: + """Remove a predicate label if it exists.""" + + if label in cls._predicates: + cls._predicates.remove(label) + return True + return False diff --git a/meshmind/pipeline/compress.py b/meshmind/pipeline/compress.py index 9ded746..c7eb995 100644 --- a/meshmind/pipeline/compress.py +++ b/meshmind/pipeline/compress.py @@ -1,13 +1,11 @@ -""" -Pipeline for token-aware compression/summarization of memories. -""" +"""Token-aware compression helpers for memory metadata.""" +from __future__ import annotations + from typing import List -from meshmind.core.types import Memory -try: - import tiktoken -except ImportError: - tiktoken = None # type: ignore +from meshmind.core.observability import log_event, telemetry +from meshmind.core.types import Memory +from meshmind.core.utils import get_token_encoder def compress_memories( @@ -20,19 +18,29 @@ def compress_memories( :param max_tokens: Maximum number of tokens allowed per memory. :return: List of Memory objects with content possibly shortened. """ - encoder = tiktoken.get_encoding('o200k_base') + log_event("pipeline.compress.start", items=len(memories)) + encoder = get_token_encoder("o200k_base", optional=True) + if encoder is None: + telemetry.increment("pipeline.compress.skipped", len(memories)) + return memories compressed = [] - for mem in memories: - content = mem.metadata.get('content') - if not isinstance(content, str): - compressed.append(mem) - continue - tokens = encoder.encode(content) - if len(tokens) <= max_tokens: + modified = 0 + with telemetry.track_duration("pipeline.compress.duration"): + for mem in memories: + content = mem.metadata.get('content') + if not isinstance(content, str): + compressed.append(mem) + continue + tokens = encoder.encode(content) + if len(tokens) <= max_tokens: + compressed.append(mem) + continue + # Truncate tokens and decode back to string + truncated = encoder.decode(tokens[:max_tokens]) + mem.metadata['content'] = truncated compressed.append(mem) - continue - # Truncate tokens and decode back to string - truncated = encoder.decode(tokens[:max_tokens]) - mem.metadata['content'] = truncated - compressed.append(mem) - return compressed \ No newline at end of file + modified += 1 + telemetry.increment("pipeline.compress.processed", len(memories)) + telemetry.increment("pipeline.compress.modified", modified) + log_event("pipeline.compress.complete", modified=modified) + return compressed diff --git a/meshmind/pipeline/consolidate.py b/meshmind/pipeline/consolidate.py index e3f4dc3..398007e 100644 --- a/meshmind/pipeline/consolidate.py +++ b/meshmind/pipeline/consolidate.py @@ -1,29 +1,157 @@ -""" -Pipeline for consolidating and summarizing duplicate memories. -""" -from typing import List, Any +"""Pipeline helpers for consolidating and summarising duplicate memories.""" +from __future__ import annotations + +from dataclasses import dataclass +from dataclasses import dataclass, field +from typing import Dict, Iterable, List, Optional from meshmind.core.types import Memory -def consolidate_memories(memories: List[Memory]) -> List[Memory]: - """ - Consolidate duplicate memories by name, preferring high importance. +@dataclass +class ConsolidationOutcome: + """Result of merging a group of related memories.""" + + updated: Memory + removed_ids: List[str] + skipped_ids: List[str] = field(default_factory=list) + + +@dataclass +class ConsolidationSettings: + """Constraints used when planning consolidation batches.""" + + max_group_size: int = 50 + max_updates: int = 200 + max_updates_per_namespace: int = 40 + + +@dataclass +class ConsolidationPlan: + """Container for consolidation outcomes and any skipped groups.""" + + outcomes: List[ConsolidationOutcome] = field(default_factory=list) + skipped_groups: Dict[str, int] = field(default_factory=dict) + + def add_skipped(self, namespace: str, count: int) -> None: + self.skipped_groups[namespace] = self.skipped_groups.get(namespace, 0) + count + + def __iter__(self): + return iter(self.outcomes) + + def __len__(self) -> int: + return len(self.outcomes) - :param memories: List of Memory objects. - :return: List of consolidated Memory objects. - """ - # Group memories by name - grouped: dict[str, List[Memory]] = {} + +def _merge_metadata(group: Iterable[Memory]) -> Dict[str, object]: + merged: Dict[str, object] = {} + for mem in group: + metadata = getattr(mem, "metadata", {}) or {} + if not isinstance(metadata, dict): + continue + for key, value in metadata.items(): + if key not in merged: + merged[key] = value + continue + existing = merged[key] + if existing == value: + continue + if not isinstance(existing, list): + existing = [existing] + if isinstance(value, list): + for item in value: + if item not in existing: + existing.append(item) + else: + if value not in existing: + existing.append(value) + merged[key] = existing + return merged + + +def _combine_embeddings(group: Iterable[Memory]) -> List[float] | None: + embeddings: List[List[float]] = [] + for mem in group: + emb = getattr(mem, "embedding", None) + if isinstance(emb, list) and emb: + embeddings.append([float(x) for x in emb]) + if not embeddings: + return None + length = len(embeddings[0]) + sums = [0.0] * length + for vector in embeddings: + if len(vector) != length: + continue + for idx, value in enumerate(vector): + sums[idx] += value + count = max(len(embeddings), 1) + return [round(total / count, 6) for total in sums] + + +def _summary_from_group(group: Iterable[Memory]) -> str: + seen: List[str] = [] + for mem in group: + text = "" + metadata = getattr(mem, "metadata", {}) or {} + if isinstance(metadata, dict): + text = str(metadata.get("content") or metadata.get("summary") or "") + if not text: + text = getattr(mem, "name", "") + text = text.strip() + if text and text not in seen: + seen.append(text) + return " \n".join(seen[:3]) + + +def consolidate_memories( + memories: List[Memory], + settings: Optional[ConsolidationSettings] = None, +) -> ConsolidationPlan: + """Consolidate duplicate memories and describe the merge plan.""" + + grouped: Dict[tuple[str, str, str], List[Memory]] = {} for mem in memories: - grouped.setdefault(mem.name, []).append(mem) + key = (getattr(mem, "namespace", ""), getattr(mem, "entity_label", ""), getattr(mem, "name", "")) + grouped.setdefault(key, []).append(mem) + + cfg = settings or ConsolidationSettings() + plan = ConsolidationPlan() + namespace_counts: Dict[str, int] = {} + + for group in grouped.values(): + if len(group) == 1: + continue + namespace = getattr(group[0], "namespace", "") or "default" + if len(group) > cfg.max_group_size: + plan.add_skipped(namespace, len(group)) + continue + if len(plan) >= cfg.max_updates: + plan.add_skipped(namespace, len(group)) + continue + if namespace_counts.get(namespace, 0) >= cfg.max_updates_per_namespace: + plan.add_skipped(namespace, len(group)) + continue - consolidated: List[Memory] = [] - for name, group in grouped.items(): - # Choose the memory with highest importance (fallback to first) - selected = max( + primary = max( group, - key=lambda m: (m.importance or 0.0), + key=lambda m: ( + getattr(m, "importance", 0.0) or 0.0, + getattr(m, "updated_at", getattr(m, "created_at", None)), + ), ) - consolidated.append(selected) - return consolidated \ No newline at end of file + metadata = _merge_metadata(group) + summary = _summary_from_group(group) + if summary: + metadata.setdefault("consolidated_summary", summary) + embedding = _combine_embeddings(group) + update: Dict[str, object] = {"metadata": metadata} + if embedding is not None: + update["embedding"] = embedding + if metadata and "importance" not in metadata: + importance_values = [getattr(mem, "importance", 0.0) or 0.0 for mem in group] + update["importance"] = round(max(importance_values), 3) + updated = primary.model_copy(update=update) + removed_ids = [str(getattr(mem, "uuid", "")) for mem in group if mem is not primary] + namespace_counts[namespace] = namespace_counts.get(namespace, 0) + 1 + plan.outcomes.append(ConsolidationOutcome(updated=updated, removed_ids=removed_ids)) + return plan diff --git a/meshmind/pipeline/expire.py b/meshmind/pipeline/expire.py index cb8070e..57e09de 100644 --- a/meshmind/pipeline/expire.py +++ b/meshmind/pipeline/expire.py @@ -1,7 +1,5 @@ -""" -Pipeline for expiring memories with TTL. -""" -from datetime import datetime, timedelta +"""Pipeline for expiring memories with TTL.""" +from datetime import datetime, timedelta, timezone from typing import List from meshmind.api.memory_manager import MemoryManager @@ -18,7 +16,7 @@ def expire_memories(manager: MemoryManager) -> List[str]: expired = [] # List all memories memories = manager.list_memories() - now = datetime.utcnow() + now = datetime.now(timezone.utc) for mem in memories: ttl = getattr(mem, 'ttl_seconds', None) if ttl is None: diff --git a/meshmind/pipeline/extract.py b/meshmind/pipeline/extract.py index 613073b..5ea5c10 100644 --- a/meshmind/pipeline/extract.py +++ b/meshmind/pipeline/extract.py @@ -1,12 +1,17 @@ -from typing import Any, List, Type +from typing import Any, List, Sequence, Type + +from meshmind.core.observability import log_event, telemetry +from meshmind.core.config import settings +from meshmind.llm_client import LLMClient, LLMConfig, build_llm_config_from_settings def extract_memories( instructions: str, namespace: str, - entity_types: List[Type[Any]], + entity_types: Sequence[Type[Any]], embedding_model: str, - content: List[str], + content: Sequence[str], llm_client: Any = None, + llm_config: LLMConfig | None = None, ) -> List[Any]: """ Extract structured Memory objects from provided content using an LLM. @@ -20,19 +25,19 @@ def extract_memories( :return: List of extracted Memory-like dicts or objects. """ import json - try: - from openai import OpenAI - except ImportError: - raise RuntimeError("openai package is required for extraction pipeline") from meshmind.core.types import Memory from meshmind.core.embeddings import EncoderRegistry + from meshmind.models.registry import EntityRegistry + + log_event("pipeline.extract.start", segments=len(content)) # Initialize default LLM client if not provided if llm_client is None: - llm_client = OpenAI() + config = llm_config or build_llm_config_from_settings(settings) + llm_client = LLMClient(config) # Prepare function schema for Memory items - mem_schema = Memory.schema() + mem_schema = Memory.model_json_schema() function_spec = { "name": "extract_memories", "description": "Extract structured memories from text segments", @@ -46,6 +51,9 @@ def extract_memories( } # Build system prompt using a default template and user instructions + entity_types = list(entity_types) or [Memory] + for model in entity_types: + EntityRegistry.register(model) allowed_labels = [cls.__name__ for cls in entity_types] default_prompt = ( "You are an agent that extracts structured memories from text segments. " @@ -58,15 +66,16 @@ def extract_memories( prompt += f"\nAllowed entity labels: {', '.join(allowed_labels)}." messages = [{"role": "system", "content": prompt}] # Add each text segment as a user message - messages += [{"role": "user", "content": text} for text in content] + messages += [{"role": "user", "content": text} for text in list(content)] # Call chat completion with function-calling - response = llm_client.responses.create( - model="gpt-4.1-mini", - messages=messages, - functions=[function_spec], - function_call={"name": "extract_memories"}, - ) + with telemetry.track_duration("pipeline.extract.duration"): + response = llm_client.responses.create( + operation="extraction", + messages=messages, + functions=[function_spec], + function_call={"name": "extract_memories"}, + ) msg = response.choices[0].message # Parse function call arguments or direct JSON if msg.get("function_call"): @@ -99,4 +108,8 @@ def extract_memories( emb = encoder.encode([mem.name])[0] mem.embedding = emb memories.append(mem) - return memories \ No newline at end of file + + telemetry.increment("pipeline.extract.segments", len(content)) + telemetry.increment("pipeline.extract.memories", len(memories)) + log_event("pipeline.extract.complete", memories=len(memories)) + return memories diff --git a/meshmind/pipeline/preprocess.py b/meshmind/pipeline/preprocess.py index fde0077..53d7a39 100644 --- a/meshmind/pipeline/preprocess.py +++ b/meshmind/pipeline/preprocess.py @@ -1,4 +1,12 @@ -from typing import Any, List +from __future__ import annotations + +from datetime import datetime, timezone +from typing import Any, Dict, List + +import math +import re + +from meshmind.core.observability import log_event, telemetry def deduplicate(memories: List[Any], threshold: float = 0.95) -> List[Any]: """ @@ -40,22 +48,106 @@ def deduplicate(memories: List[Any], threshold: float = 0.95) -> List[Any]: unique.append(mem) return unique +def _text_from_memory(mem: Any) -> str: + chunks: List[str] = [] + name = getattr(mem, "name", None) + if isinstance(name, str): + chunks.append(name) + metadata = getattr(mem, "metadata", None) or {} + if isinstance(metadata, dict): + for value in metadata.values(): + if isinstance(value, str): + chunks.append(value) + return " ".join(chunks) + + +def _recent_bonus(reference_time: Any, now: datetime) -> float: + if reference_time is None: + return 0.0 + try: + if isinstance(reference_time, datetime): + ref = reference_time + else: + ref = datetime.fromisoformat(str(reference_time)) + except Exception: + return 0.0 + if ref.tzinfo is None: + ref = ref.replace(tzinfo=timezone.utc) + delta = now - ref + seconds = max(delta.total_seconds(), 0.0) + week = 7 * 24 * 60 * 60 + return max(0.0, 1.0 - min(seconds / week, 1.0)) + + def score_importance(memories: List[Any]) -> List[Any]: - """ - Assign or update importance scores for each Memory. + """Assign a heuristic importance score for each memory.""" - :param memories: List of Memory-like objects. - :return: List of Memory-like objects with updated importance. - """ - # Assign a default importance score if missing + now = datetime.now(timezone.utc) for mem in memories: - if getattr(mem, 'importance', None) is None: - try: - mem.importance = 1.0 - except Exception: + try: + if getattr(mem, "importance", None) not in (None, 0): continue + + text = _text_from_memory(mem) + tokens = re.findall(r"\w+", text.lower()) + unique_ratio = len(set(tokens)) / max(len(tokens), 1) + length_factor = min(len(tokens) / 25.0, 1.5) + digit_bonus = 0.3 if any(ch.isdigit() for ch in text) else 0.0 + recency_bonus = _recent_bonus(getattr(mem, "reference_time", None), now) + metadata_size = len(getattr(mem, "metadata", {}) or {}) + metadata_bonus = min(metadata_size * 0.05, 0.25) + + embedding = getattr(mem, "embedding", None) or [] + magnitude = math.sqrt(sum((float(x) ** 2 for x in embedding))) if embedding else 0.0 + magnitude_bonus = min(magnitude / 10.0, 0.4) + + score = 0.5 + (0.8 * unique_ratio) + length_factor + digit_bonus + score += recency_bonus + metadata_bonus + magnitude_bonus + mem.importance = round(min(score, 5.0), 3) + except Exception: + continue + metrics = summarize_importance(memories) + telemetry.gauge("importance.mean", metrics["mean"]) + telemetry.gauge("importance.stddev", metrics["stddev"]) + telemetry.gauge("importance.count", float(metrics["count"])) + log_event( + "importance.scored", + mean=metrics["mean"], + stddev=metrics["stddev"], + count=metrics["count"], + recent=metrics["recent_bonus"], + ) return memories + +def summarize_importance(memories: List[Any]) -> Dict[str, float]: + """Return descriptive statistics about importance assignments.""" + + values: List[float] = [] + recency_bonus_total = 0.0 + now = datetime.now(timezone.utc) + for mem in memories: + val = getattr(mem, "importance", None) + if val is None: + continue + try: + values.append(float(val)) + recency_bonus_total += _recent_bonus(getattr(mem, "reference_time", None), now) + except Exception: + continue + count = len(values) + if not values: + return {"mean": 0.0, "stddev": 0.0, "count": 0.0, "recent_bonus": 0.0} + mean_value = sum(values) / count + variance = sum((value - mean_value) ** 2 for value in values) / count + stddev = math.sqrt(variance) + return { + "mean": round(mean_value, 3), + "stddev": round(stddev, 3), + "count": float(count), + "recent_bonus": round(recency_bonus_total / count, 3), + } + def compress(memories: List[Any]) -> List[Any]: """ Compress long text fields in Memory objects to save space or reduce tokens. diff --git a/meshmind/pipeline/store.py b/meshmind/pipeline/store.py index 3d0b07c..8a340a5 100644 --- a/meshmind/pipeline/store.py +++ b/meshmind/pipeline/store.py @@ -1,23 +1,71 @@ +"""Persistence helpers for storing memories and triplets.""" +from __future__ import annotations + from typing import Any, Iterable + +from pydantic import BaseModel + +from meshmind.core.observability import log_event, telemetry +from meshmind.core.types import Triplet from meshmind.db.base_driver import GraphDriver +from meshmind.models.registry import EntityRegistry, PredicateRegistry + + +def _props(obj: Any) -> dict[str, Any]: + if isinstance(obj, BaseModel): + return obj.model_dump(exclude_none=True) + if hasattr(obj, "dict") or hasattr(obj, "model_dump"): + try: + return obj.model_dump(exclude_none=True) # type: ignore[attr-defined] + except (TypeError, AttributeError): + pass + if isinstance(obj, dict): + return {k: v for k, v in obj.items() if v is not None} + return {k: v for k, v in obj.__dict__.items() if v is not None} + def store_memories( memories: Iterable[Any], graph_driver: GraphDriver, + *, + entity_registry: type[EntityRegistry] | None = None, ) -> None: - """ - Persist a sequence of Memory objects into the graph database. - - :param memories: An iterable of Memory-like objects with attributes for upsert. - :param graph_driver: An instance of GraphDriver to perform database operations. - """ - # Iterate over Memory-like objects and upsert into graph - for mem in memories: - # Use Pydantic-like dict to extract properties - try: - props = mem.dict(exclude_none=True) - except Exception: - # Fallback for non-Pydantic objects - props = mem.__dict__ - # Upsert entity node with label and name - graph_driver.upsert_entity(mem.entity_label, mem.name, props) \ No newline at end of file + """Persist a sequence of Memory objects into the graph database.""" + + registry = entity_registry or EntityRegistry + stored = 0 + with telemetry.track_duration("pipeline.store.memories.duration"): + for mem in memories: + props = _props(mem) + label = getattr(mem, "entity_label", None) + if label and registry.model_for_label(label) is None and isinstance(mem, BaseModel): + registry.register(type(mem)) + graph_driver.upsert_entity(label or "Memory", getattr(mem, "name", ""), props) + stored += 1 + telemetry.increment("pipeline.store.memories.stored", stored) + log_event("pipeline.store.memories", count=stored) + + +def store_triplets( + triplets: Iterable[Triplet], + graph_driver: GraphDriver, + *, + predicate_registry: type[PredicateRegistry] | None = None, +) -> None: + """Persist a collection of ``Triplet`` relationships.""" + + registry = predicate_registry or PredicateRegistry + stored = 0 + with telemetry.track_duration("pipeline.store.triplets.duration"): + for triplet in triplets: + registry.add(triplet.predicate) + props = _props(triplet) + graph_driver.upsert_edge( + triplet.subject, + triplet.predicate, + triplet.object, + props, + ) + stored += 1 + telemetry.increment("pipeline.store.triplets.stored", stored) + log_event("pipeline.store.triplets", count=stored) diff --git a/meshmind/protos/__init__.py b/meshmind/protos/__init__.py new file mode 100644 index 0000000..1c4e68a --- /dev/null +++ b/meshmind/protos/__init__.py @@ -0,0 +1,12 @@ +"""Generated gRPC protocol buffers for MeshMind.""" + +from importlib import resources + +__all__ = ["data_path"] + + +def data_path(*parts: str) -> str: + """Return an absolute path to packaged proto/data files.""" + + with resources.as_file(resources.files(__package__).joinpath(*parts)) as path: + return str(path) diff --git a/meshmind/protos/memory_service.proto b/meshmind/protos/memory_service.proto new file mode 100644 index 0000000..2628e93 --- /dev/null +++ b/meshmind/protos/memory_service.proto @@ -0,0 +1,83 @@ +syntax = "proto3"; + +package meshmind.api.v1; + +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +message MemoryPayload { + string uuid = 1; + string namespace = 2; + string name = 3; + string entity_label = 4; + repeated double embedding = 5; + google.protobuf.Struct metadata = 6; + string reference_time = 7; + google.protobuf.DoubleValue importance = 8; + google.protobuf.Int64Value ttl_seconds = 9; + string created_at = 10; + string updated_at = 11; +} + +message TripletPayload { + string subject = 1; + string predicate = 2; + string object = 3; + string namespace = 4; + string entity_label = 5; + google.protobuf.Struct metadata = 6; + string reference_time = 7; +} + +message SearchPayload { + string query = 1; + string namespace = 2; + int32 top_k = 3; + string encoder = 4; + google.protobuf.Int32Value rerank_k = 5; + repeated string entity_labels = 6; + string rerank_model = 7; + bool use_llm_rerank = 8; + map llm_models = 9; + map llm_base_urls = 10; + string llm_api_key = 11; +} + +message IngestMemoriesRequest { + repeated MemoryPayload memories = 1; +} + +message IngestMemoriesResponse { + repeated string uuids = 1; +} + +message IngestTripletsRequest { + repeated TripletPayload triplets = 1; +} + +message IngestTripletsResponse { + int32 stored = 1; +} + +message SearchResponse { + repeated MemoryPayload results = 1; +} + +message MemoryCountsRequest { + string namespace = 1; +} + +message NamespaceCounts { + map entity_counts = 1; +} + +message MemoryCountsResponse { + map counts = 1; +} + +service MeshMindService { + rpc IngestMemories(IngestMemoriesRequest) returns (IngestMemoriesResponse); + rpc IngestTriplets(IngestTripletsRequest) returns (IngestTripletsResponse); + rpc Search(SearchPayload) returns (SearchResponse); + rpc MemoryCounts(MemoryCountsRequest) returns (MemoryCountsResponse); +} diff --git a/meshmind/protos/memory_service_pb2.py b/meshmind/protos/memory_service_pb2.py new file mode 100644 index 0000000..4d7b65e --- /dev/null +++ b/meshmind/protos/memory_service_pb2.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: memory_service.proto +# Protobuf Python Version: 6.31.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 31, + 1, + '', + 'memory_service.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 +from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14memory_service.proto\x12\x0fmeshmind.api.v1\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xb6\x02\n\rMemoryPayload\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x11\n\tnamespace\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x14\n\x0c\x65ntity_label\x18\x04 \x01(\t\x12\x11\n\tembedding\x18\x05 \x03(\x01\x12)\n\x08metadata\x18\x06 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x16\n\x0ereference_time\x18\x07 \x01(\t\x12\x30\n\nimportance\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.DoubleValue\x12\x30\n\x0bttl_seconds\x18\t \x01(\x0b\x32\x1b.google.protobuf.Int64Value\x12\x12\n\ncreated_at\x18\n \x01(\t\x12\x12\n\nupdated_at\x18\x0b \x01(\t\"\xb0\x01\n\x0eTripletPayload\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x0e\n\x06object\x18\x03 \x01(\t\x12\x11\n\tnamespace\x18\x04 \x01(\t\x12\x14\n\x0c\x65ntity_label\x18\x05 \x01(\t\x12)\n\x08metadata\x18\x06 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x16\n\x0ereference_time\x18\x07 \x01(\t\"\xcb\x03\n\rSearchPayload\x12\r\n\x05query\x18\x01 \x01(\t\x12\x11\n\tnamespace\x18\x02 \x01(\t\x12\r\n\x05top_k\x18\x03 \x01(\x05\x12\x0f\n\x07\x65ncoder\x18\x04 \x01(\t\x12-\n\x08rerank_k\x18\x05 \x01(\x0b\x32\x1b.google.protobuf.Int32Value\x12\x15\n\rentity_labels\x18\x06 \x03(\t\x12\x14\n\x0crerank_model\x18\x07 \x01(\t\x12\x16\n\x0euse_llm_rerank\x18\x08 \x01(\x08\x12\x41\n\nllm_models\x18\t \x03(\x0b\x32-.meshmind.api.v1.SearchPayload.LlmModelsEntry\x12\x46\n\rllm_base_urls\x18\n \x03(\x0b\x32/.meshmind.api.v1.SearchPayload.LlmBaseUrlsEntry\x12\x13\n\x0bllm_api_key\x18\x0b \x01(\t\x1a\x30\n\x0eLlmModelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x32\n\x10LlmBaseUrlsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"I\n\x15IngestMemoriesRequest\x12\x30\n\x08memories\x18\x01 \x03(\x0b\x32\x1e.meshmind.api.v1.MemoryPayload\"\'\n\x16IngestMemoriesResponse\x12\r\n\x05uuids\x18\x01 \x03(\t\"J\n\x15IngestTripletsRequest\x12\x31\n\x08triplets\x18\x01 \x03(\x0b\x32\x1f.meshmind.api.v1.TripletPayload\"(\n\x16IngestTripletsResponse\x12\x0e\n\x06stored\x18\x01 \x01(\x05\"A\n\x0eSearchResponse\x12/\n\x07results\x18\x01 \x03(\x0b\x32\x1e.meshmind.api.v1.MemoryPayload\"(\n\x13MemoryCountsRequest\x12\x11\n\tnamespace\x18\x01 \x01(\t\"\x91\x01\n\x0fNamespaceCounts\x12I\n\rentity_counts\x18\x01 \x03(\x0b\x32\x32.meshmind.api.v1.NamespaceCounts.EntityCountsEntry\x1a\x33\n\x11\x45ntityCountsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x03:\x02\x38\x01\"\xaa\x01\n\x14MemoryCountsResponse\x12\x41\n\x06\x63ounts\x18\x01 \x03(\x0b\x32\x31.meshmind.api.v1.MemoryCountsResponse.CountsEntry\x1aO\n\x0b\x43ountsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12/\n\x05value\x18\x02 \x01(\x0b\x32 .meshmind.api.v1.NamespaceCounts:\x02\x38\x01\x32\xff\x02\n\x0fMeshMindService\x12\x61\n\x0eIngestMemories\x12&.meshmind.api.v1.IngestMemoriesRequest\x1a\'.meshmind.api.v1.IngestMemoriesResponse\x12\x61\n\x0eIngestTriplets\x12&.meshmind.api.v1.IngestTripletsRequest\x1a\'.meshmind.api.v1.IngestTripletsResponse\x12I\n\x06Search\x12\x1e.meshmind.api.v1.SearchPayload\x1a\x1f.meshmind.api.v1.SearchResponse\x12[\n\x0cMemoryCounts\x12$.meshmind.api.v1.MemoryCountsRequest\x1a%.meshmind.api.v1.MemoryCountsResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'memory_service_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_SEARCHPAYLOAD_LLMMODELSENTRY']._loaded_options = None + _globals['_SEARCHPAYLOAD_LLMMODELSENTRY']._serialized_options = b'8\001' + _globals['_SEARCHPAYLOAD_LLMBASEURLSENTRY']._loaded_options = None + _globals['_SEARCHPAYLOAD_LLMBASEURLSENTRY']._serialized_options = b'8\001' + _globals['_NAMESPACECOUNTS_ENTITYCOUNTSENTRY']._loaded_options = None + _globals['_NAMESPACECOUNTS_ENTITYCOUNTSENTRY']._serialized_options = b'8\001' + _globals['_MEMORYCOUNTSRESPONSE_COUNTSENTRY']._loaded_options = None + _globals['_MEMORYCOUNTSRESPONSE_COUNTSENTRY']._serialized_options = b'8\001' + _globals['_MEMORYPAYLOAD']._serialized_start=104 + _globals['_MEMORYPAYLOAD']._serialized_end=414 + _globals['_TRIPLETPAYLOAD']._serialized_start=417 + _globals['_TRIPLETPAYLOAD']._serialized_end=593 + _globals['_SEARCHPAYLOAD']._serialized_start=596 + _globals['_SEARCHPAYLOAD']._serialized_end=1055 + _globals['_SEARCHPAYLOAD_LLMMODELSENTRY']._serialized_start=955 + _globals['_SEARCHPAYLOAD_LLMMODELSENTRY']._serialized_end=1003 + _globals['_SEARCHPAYLOAD_LLMBASEURLSENTRY']._serialized_start=1005 + _globals['_SEARCHPAYLOAD_LLMBASEURLSENTRY']._serialized_end=1055 + _globals['_INGESTMEMORIESREQUEST']._serialized_start=1057 + _globals['_INGESTMEMORIESREQUEST']._serialized_end=1130 + _globals['_INGESTMEMORIESRESPONSE']._serialized_start=1132 + _globals['_INGESTMEMORIESRESPONSE']._serialized_end=1171 + _globals['_INGESTTRIPLETSREQUEST']._serialized_start=1173 + _globals['_INGESTTRIPLETSREQUEST']._serialized_end=1247 + _globals['_INGESTTRIPLETSRESPONSE']._serialized_start=1249 + _globals['_INGESTTRIPLETSRESPONSE']._serialized_end=1289 + _globals['_SEARCHRESPONSE']._serialized_start=1291 + _globals['_SEARCHRESPONSE']._serialized_end=1356 + _globals['_MEMORYCOUNTSREQUEST']._serialized_start=1358 + _globals['_MEMORYCOUNTSREQUEST']._serialized_end=1398 + _globals['_NAMESPACECOUNTS']._serialized_start=1401 + _globals['_NAMESPACECOUNTS']._serialized_end=1546 + _globals['_NAMESPACECOUNTS_ENTITYCOUNTSENTRY']._serialized_start=1495 + _globals['_NAMESPACECOUNTS_ENTITYCOUNTSENTRY']._serialized_end=1546 + _globals['_MEMORYCOUNTSRESPONSE']._serialized_start=1549 + _globals['_MEMORYCOUNTSRESPONSE']._serialized_end=1719 + _globals['_MEMORYCOUNTSRESPONSE_COUNTSENTRY']._serialized_start=1640 + _globals['_MEMORYCOUNTSRESPONSE_COUNTSENTRY']._serialized_end=1719 + _globals['_MESHMINDSERVICE']._serialized_start=1722 + _globals['_MESHMINDSERVICE']._serialized_end=2105 +# @@protoc_insertion_point(module_scope) diff --git a/meshmind/protos/memory_service_pb2_grpc.py b/meshmind/protos/memory_service_pb2_grpc.py new file mode 100644 index 0000000..0db1c72 --- /dev/null +++ b/meshmind/protos/memory_service_pb2_grpc.py @@ -0,0 +1,226 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from . import memory_service_pb2 as memory__service__pb2 + +GRPC_GENERATED_VERSION = '1.75.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in memory_service_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class MeshMindServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.IngestMemories = channel.unary_unary( + '/meshmind.api.v1.MeshMindService/IngestMemories', + request_serializer=memory__service__pb2.IngestMemoriesRequest.SerializeToString, + response_deserializer=memory__service__pb2.IngestMemoriesResponse.FromString, + _registered_method=True) + self.IngestTriplets = channel.unary_unary( + '/meshmind.api.v1.MeshMindService/IngestTriplets', + request_serializer=memory__service__pb2.IngestTripletsRequest.SerializeToString, + response_deserializer=memory__service__pb2.IngestTripletsResponse.FromString, + _registered_method=True) + self.Search = channel.unary_unary( + '/meshmind.api.v1.MeshMindService/Search', + request_serializer=memory__service__pb2.SearchPayload.SerializeToString, + response_deserializer=memory__service__pb2.SearchResponse.FromString, + _registered_method=True) + self.MemoryCounts = channel.unary_unary( + '/meshmind.api.v1.MeshMindService/MemoryCounts', + request_serializer=memory__service__pb2.MemoryCountsRequest.SerializeToString, + response_deserializer=memory__service__pb2.MemoryCountsResponse.FromString, + _registered_method=True) + + +class MeshMindServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def IngestMemories(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def IngestTriplets(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Search(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def MemoryCounts(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_MeshMindServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'IngestMemories': grpc.unary_unary_rpc_method_handler( + servicer.IngestMemories, + request_deserializer=memory__service__pb2.IngestMemoriesRequest.FromString, + response_serializer=memory__service__pb2.IngestMemoriesResponse.SerializeToString, + ), + 'IngestTriplets': grpc.unary_unary_rpc_method_handler( + servicer.IngestTriplets, + request_deserializer=memory__service__pb2.IngestTripletsRequest.FromString, + response_serializer=memory__service__pb2.IngestTripletsResponse.SerializeToString, + ), + 'Search': grpc.unary_unary_rpc_method_handler( + servicer.Search, + request_deserializer=memory__service__pb2.SearchPayload.FromString, + response_serializer=memory__service__pb2.SearchResponse.SerializeToString, + ), + 'MemoryCounts': grpc.unary_unary_rpc_method_handler( + servicer.MemoryCounts, + request_deserializer=memory__service__pb2.MemoryCountsRequest.FromString, + response_serializer=memory__service__pb2.MemoryCountsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'meshmind.api.v1.MeshMindService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('meshmind.api.v1.MeshMindService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class MeshMindService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def IngestMemories(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/meshmind.api.v1.MeshMindService/IngestMemories', + memory__service__pb2.IngestMemoriesRequest.SerializeToString, + memory__service__pb2.IngestMemoriesResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def IngestTriplets(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/meshmind.api.v1.MeshMindService/IngestTriplets', + memory__service__pb2.IngestTripletsRequest.SerializeToString, + memory__service__pb2.IngestTripletsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Search(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/meshmind.api.v1.MeshMindService/Search', + memory__service__pb2.SearchPayload.SerializeToString, + memory__service__pb2.SearchResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def MemoryCounts(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/meshmind.api.v1.MeshMindService/MemoryCounts', + memory__service__pb2.MemoryCountsRequest.SerializeToString, + memory__service__pb2.MemoryCountsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/meshmind/retrieval/__init__.py b/meshmind/retrieval/__init__.py index e69de29..4239b7f 100644 --- a/meshmind/retrieval/__init__.py +++ b/meshmind/retrieval/__init__.py @@ -0,0 +1,39 @@ +"""Retrieval helpers exposed for external consumers.""" + +from .search import ( + search, + search_bm25, + search_exact, + search_fuzzy, + search_regex, + search_vector, +) +from .graph import ( + graph_bm25_search, + graph_exact_search, + graph_fuzzy_search, + graph_hybrid_search, + graph_regex_search, + graph_vector_search, +) +from .vector import vector_search, vector_search_from_embeddings +from .rerank import llm_rerank, apply_reranker + +__all__ = [ + "search", + "search_bm25", + "search_exact", + "search_fuzzy", + "search_regex", + "search_vector", + "graph_hybrid_search", + "graph_vector_search", + "graph_regex_search", + "graph_exact_search", + "graph_bm25_search", + "graph_fuzzy_search", + "vector_search", + "vector_search_from_embeddings", + "llm_rerank", + "apply_reranker", +] diff --git a/meshmind/retrieval/bm25.py b/meshmind/retrieval/bm25.py index 6158b03..a68c1d5 100644 --- a/meshmind/retrieval/bm25.py +++ b/meshmind/retrieval/bm25.py @@ -1,9 +1,17 @@ -""" -TF-IDF based retrieval (approximate BM25) using scikit-learn. -""" +"""TF-IDF based retrieval (approximate BM25) using scikit-learn or fallbacks.""" +from __future__ import annotations + +import math +import re +from collections import Counter from typing import List, Tuple -from sklearn.feature_extraction.text import TfidfVectorizer -from sklearn.metrics.pairwise import cosine_similarity + +try: # pragma: no cover - optional dependency + from sklearn.feature_extraction.text import TfidfVectorizer + from sklearn.metrics.pairwise import cosine_similarity +except ImportError: # pragma: no cover - exercised when sklearn is unavailable + TfidfVectorizer = None # type: ignore + cosine_similarity = None # type: ignore from meshmind.core.types import Memory @@ -21,24 +29,52 @@ def bm25_search( :param top_k: Number of top results to return. :return: List of (Memory, similarity_score) tuples. """ - # Prepare document texts docs = [mem.name for mem in memories] - # Vectorize documents and query - vectorizer = TfidfVectorizer() - tfidf_matrix = vectorizer.fit_transform(docs) - query_vec = vectorizer.transform([query]) - # Compute cosine similarity scores - scores = cosine_similarity(query_vec, tfidf_matrix)[0] - # Rank scores - ranked = sorted( - enumerate(scores), key=lambda x: x[1], reverse=True - ) - # Collect top_k non-zero scores + + if TfidfVectorizer is not None and cosine_similarity is not None: + vectorizer = TfidfVectorizer() + tfidf_matrix = vectorizer.fit_transform(docs) + query_vec = vectorizer.transform([query]) + scores = cosine_similarity(query_vec, tfidf_matrix)[0] + ranked = sorted(enumerate(scores), key=lambda x: x[1], reverse=True) + results: List[Tuple[Memory, float]] = [] + for idx, score in ranked: + if score <= 0: + break + results.append((memories[idx], float(score))) + if len(results) >= top_k: + break + return results + + tokens = [_tokenize(doc) for doc in docs] + query_tokens = Counter(_tokenize(query)) + doc_freq: Counter[str] = Counter() + for tok_set in map(set, tokens): + for token in tok_set: + doc_freq[token] += 1 + + scores: List[Tuple[int, float]] = [] + total_docs = max(len(tokens), 1) + for idx, doc_tokens in enumerate(tokens): + doc_count = Counter(doc_tokens) + doc_len = len(doc_tokens) or 1 + score = 0.0 + for token, q_tf in query_tokens.items(): + tf = doc_count.get(token, 0) / doc_len + idf = math.log((total_docs + 1) / (doc_freq.get(token, 0) + 1)) + 1.0 + score += tf * idf * q_tf + scores.append((idx, score)) + + ranked = sorted(scores, key=lambda x: x[1], reverse=True) results: List[Tuple[Memory, float]] = [] for idx, score in ranked: if score <= 0: - break + continue results.append((memories[idx], float(score))) if len(results) >= top_k: break - return results \ No newline at end of file + return results + + +def _tokenize(text: str) -> List[str]: + return re.findall(r"\w+", text.lower()) diff --git a/meshmind/retrieval/fuzzy.py b/meshmind/retrieval/fuzzy.py index bd762e0..461da73 100644 --- a/meshmind/retrieval/fuzzy.py +++ b/meshmind/retrieval/fuzzy.py @@ -1,8 +1,14 @@ -""" -Fuzzy string matching retrieval using rapidfuzz. -""" -from typing import List, Tuple -from rapidfuzz import process, fuzz +"""Fuzzy string matching retrieval with optional ``rapidfuzz`` acceleration.""" +from __future__ import annotations + +from difflib import SequenceMatcher +from typing import Callable, List, Tuple + +try: # pragma: no cover - optional dependency + from rapidfuzz import fuzz, process +except ImportError: # pragma: no cover - fallback for environments without rapidfuzz + fuzz = None # type: ignore + process = None # type: ignore from meshmind.core.types import Memory @@ -22,19 +28,31 @@ def fuzzy_search( :param score_cutoff: Minimum score (0-1) to include in results. :return: List of (Memory, normalized_score) tuples. """ - # Build choices mapping choices = [mem.name for mem in memories] - # rapidfuzz returns scores in 0-100 range - raw_results = process.extract( - query, - choices, - scorer=fuzz.WRatio, - limit=top_k, - score_cutoff=score_cutoff * 100, - ) + + if process is not None and fuzz is not None: + raw_results = process.extract( + query, + choices, + scorer=fuzz.WRatio, + limit=top_k, + score_cutoff=score_cutoff * 100, + ) + results: List[Tuple[Memory, float]] = [] + for match, score, idx in raw_results: + results.append((memories[idx], score / 100.0)) + return results + + scorer: Callable[[str, str], float] = _sequence_ratio results: List[Tuple[Memory, float]] = [] - for match, score, idx in raw_results: - # Normalize score to [0,1] - norm = score / 100.0 - results.append((memories[idx], norm)) - return results \ No newline at end of file + for idx, name in enumerate(choices): + score = scorer(query, name) + if score < score_cutoff: + continue + results.append((memories[idx], score)) + results.sort(key=lambda item: item[1], reverse=True) + return results[:top_k] + + +def _sequence_ratio(a: str, b: str) -> float: + return SequenceMatcher(None, a.lower(), b.lower()).ratio() diff --git a/meshmind/retrieval/graph.py b/meshmind/retrieval/graph.py new file mode 100644 index 0000000..20fc96d --- /dev/null +++ b/meshmind/retrieval/graph.py @@ -0,0 +1,225 @@ +"""Graph-backed retrieval helpers that fetch memories directly from a driver.""" +from __future__ import annotations + +from typing import Callable, Iterable, List, Optional, Sequence + +from meshmind.api.memory_manager import MemoryManager +from meshmind.core.types import Memory, SearchConfig +from meshmind.db.base_driver import GraphDriver +from meshmind.retrieval.search import ( + search as hybrid_search, + search_bm25, + search_exact, + search_fuzzy, + search_regex, + search_vector, +) + +Reranker = Callable[[str, Sequence[Memory], int], Sequence[Memory]] + + +def _load_memories( + driver: GraphDriver, + namespace: Optional[str] = None, + entity_labels: Optional[Iterable[str]] = None, + *, + query: Optional[str] = None, + config: Optional[SearchConfig] = None, + top_k: Optional[int] = None, + use_search: bool = True, +) -> List[Memory]: + manager = MemoryManager(driver) + labels = _ensure_sequence(entity_labels) + candidate_limit: Optional[int] = None + if use_search: + limit_hint = 0 + if config is not None: + limit_hint = max(limit_hint, config.top_k * 5) + if config.rerank_k: + limit_hint = max(limit_hint, config.rerank_k * 2) + if top_k: + limit_hint = max(limit_hint, top_k * 5) + candidate_limit = limit_hint or None + return manager.list_memories( + namespace, + labels, + limit=candidate_limit, + query=query if use_search else None, + use_search=use_search, + ) + + +def _ensure_sequence(values: Iterable[str] | None) -> List[str] | None: + if values is None: + return None + return list(values) + + +def graph_hybrid_search( + query: str, + driver: GraphDriver, + namespace: Optional[str] = None, + entity_labels: Optional[Iterable[str]] = None, + config: Optional[SearchConfig] = None, + reranker: Reranker | None = None, +) -> List[Memory]: + """Run the standard hybrid search against memories fetched from the graph.""" + + labels = _ensure_sequence(entity_labels) + memories = _load_memories( + driver, + namespace, + labels, + query=query, + config=config, + use_search=True, + ) + return hybrid_search( + query, + memories, + namespace=namespace, + entity_labels=labels, + config=config, + reranker=reranker, + ) + + +def graph_vector_search( + query: str, + driver: GraphDriver, + namespace: Optional[str] = None, + entity_labels: Optional[Iterable[str]] = None, + config: Optional[SearchConfig] = None, +) -> List[Memory]: + """Run vector search against graph-backed memories.""" + + labels = _ensure_sequence(entity_labels) + memories = _load_memories( + driver, + namespace, + labels, + query=query, + config=config, + use_search=True, + ) + return search_vector( + query, + memories, + namespace=namespace, + entity_labels=labels, + config=config, + ) + + +def graph_regex_search( + pattern: str, + driver: GraphDriver, + namespace: Optional[str] = None, + entity_labels: Optional[Iterable[str]] = None, + flags: int | None = None, + top_k: int = 10, +) -> List[Memory]: + """Execute regex search with memories loaded from the graph.""" + + labels = _ensure_sequence(entity_labels) + memories = _load_memories( + driver, + namespace, + labels, + top_k=top_k, + use_search=False, + ) + return search_regex( + pattern, + memories, + namespace=namespace, + entity_labels=labels, + flags=flags, + top_k=top_k, + ) + + +def graph_exact_search( + query: str, + driver: GraphDriver, + namespace: Optional[str] = None, + entity_labels: Optional[Iterable[str]] = None, + fields: Optional[Iterable[str]] = None, + case_sensitive: bool = False, + top_k: int = 10, +) -> List[Memory]: + """Execute exact-match search using graph-backed memories.""" + + labels = _ensure_sequence(entity_labels) + memories = _load_memories( + driver, + namespace, + labels, + query=query, + top_k=top_k, + use_search=True, + ) + target_fields = list(fields) if fields else None + return search_exact( + query, + memories, + namespace=namespace, + entity_labels=labels, + fields=target_fields, + case_sensitive=case_sensitive, + top_k=top_k, + ) + + +def graph_bm25_search( + query: str, + driver: GraphDriver, + namespace: Optional[str] = None, + entity_labels: Optional[Iterable[str]] = None, + top_k: int = 10, +) -> List[Memory]: + """Execute BM25 search using graph-backed memories.""" + + labels = _ensure_sequence(entity_labels) + memories = _load_memories( + driver, + namespace, + labels, + query=query, + top_k=top_k, + use_search=True, + ) + return search_bm25( + query, + memories, + namespace=namespace, + entity_labels=labels, + top_k=top_k, + ) + + +def graph_fuzzy_search( + query: str, + driver: GraphDriver, + namespace: Optional[str] = None, + entity_labels: Optional[Iterable[str]] = None, + top_k: int = 10, +) -> List[Memory]: + """Execute fuzzy search against graph-backed memories.""" + + labels = _ensure_sequence(entity_labels) + memories = _load_memories( + driver, + namespace, + labels, + query=query, + top_k=top_k, + use_search=True, + ) + return search_fuzzy( + query, + memories, + namespace=namespace, + entity_labels=labels, + top_k=top_k, + ) diff --git a/meshmind/retrieval/rerank.py b/meshmind/retrieval/rerank.py new file mode 100644 index 0000000..a18b96d --- /dev/null +++ b/meshmind/retrieval/rerank.py @@ -0,0 +1,83 @@ +"""Helpers for reranking retrieval results.""" +from __future__ import annotations + +from typing import Callable, List, Sequence + +from meshmind.core.types import Memory + +Reranker = Callable[[str, Sequence[Memory], int], Sequence[Memory]] + + +def llm_rerank( + query: str, + memories: Sequence[Memory], + llm_client: object | None, + top_k: int, + model: str | None = None, + endpoint: str | None = None, +) -> List[Memory]: + """Rerank results using an LLM client that supports the Responses API.""" + if llm_client is None or not memories: + return list(memories)[:top_k] + + model_name = model or "gpt-5-nano" + prompt = "\n".join( + [ + "You are a ranking assistant.", + "Given the query and numbered memory summaries, return a JSON array of memory indexes", + "sorted from best to worst match.", + f"Query: {query}", + "Memories:", + ] + ) + for idx, memory in enumerate(memories): + prompt += f"\n{idx}: {memory.name}" + + try: # pragma: no cover - network interaction mocked in tests + response = llm_client.responses.create( # type: ignore[attr-defined] + operation="rerank", + model=model_name, + base_url=endpoint, + input=[{"role": "user", "content": prompt}], + response_format={"type": "json_schema", "json_schema": { + "name": "rankings", + "schema": { + "type": "object", + "properties": { + "order": { + "type": "array", + "items": {"type": "integer"}, + } + }, + "required": ["order"], + }, + }}, + ) + content = response.output[0].content[0].text # type: ignore[index] + except Exception: + return list(memories)[:top_k] + + try: + import json + + data = json.loads(content) + indexes = [idx for idx in data.get("order", []) if 0 <= idx < len(memories)] + except Exception: + return list(memories)[:top_k] + + ranked = [memories[idx] for idx in indexes] + remaining = [mem for mem in memories if mem not in ranked] + ranked.extend(remaining) + return ranked[:top_k] + + +def apply_reranker( + query: str, + candidates: Sequence[Memory], + top_k: int, + reranker: Reranker | None = None, +) -> List[Memory]: + if reranker is None: + return list(candidates)[:top_k] + ranked = reranker(query, candidates, top_k) + return list(ranked)[:top_k] diff --git a/meshmind/retrieval/search.py b/meshmind/retrieval/search.py index 666d7b1..f9da468 100644 --- a/meshmind/retrieval/search.py +++ b/meshmind/retrieval/search.py @@ -1,7 +1,8 @@ -""" -Unified dispatcher for various retrieval strategies. -""" -from typing import List, Optional +"""Unified dispatcher for various retrieval strategies.""" +from __future__ import annotations + +import re +from typing import Callable, List, Optional, Sequence from meshmind.core.types import Memory, SearchConfig from meshmind.retrieval.bm25 import bm25_search @@ -12,6 +13,23 @@ filter_by_entity_labels, filter_by_metadata, ) +from meshmind.retrieval.rerank import apply_reranker +from meshmind.retrieval.vector import vector_search + +Reranker = Callable[[str, Sequence[Memory], int], Sequence[Memory]] + + +def _apply_filters( + memories: Sequence[Memory], + namespace: Optional[str], + entity_labels: Optional[List[str]], + config: Optional[SearchConfig], +) -> List[Memory]: + mems = filter_by_namespace(list(memories), namespace) + mems = filter_by_entity_labels(mems, entity_labels) + if config and config.filters: + mems = filter_by_metadata(mems, config.filters) + return mems def search( @@ -20,28 +38,29 @@ def search( namespace: Optional[str] = None, entity_labels: Optional[List[str]] = None, config: Optional[SearchConfig] = None, + reranker: Reranker | None = None, ) -> List[Memory]: - """ - Perform hybrid search over memories with optional filters. - - :param query: Query string. - :param memories: List of Memory objects. - :param namespace: Filter by namespace. - :param entity_labels: Filter by entity labels. - :param config: SearchConfig overriding defaults. - :return: Ranked list of Memory objects. - """ - # Apply filters - mems = filter_by_namespace(memories, namespace) - mems = filter_by_entity_labels(mems, entity_labels) - if config and config.filters: - mems = filter_by_metadata(mems, config.filters) - - # Use hybrid search by default + """Perform hybrid search with optional reranking.""" cfg = config or SearchConfig() + mems = _apply_filters(memories, namespace, entity_labels, cfg) ranked = hybrid_search(query, mems, cfg) - # Return only Memory objects - return [m for m, _ in ranked] + baseline = [m for m, _ in ranked] + if not baseline: + return [] + + if reranker is None: + return baseline[: cfg.top_k] + + subset = mems[: cfg.rerank_k] + reranked_subset = apply_reranker(query, subset, cfg.rerank_k, reranker) + ordered: List[Memory] = [] + for mem in reranked_subset: + if mem not in ordered: + ordered.append(mem) + for mem in baseline: + if mem not in ordered: + ordered.append(mem) + return ordered[: cfg.top_k] def search_bm25( @@ -51,8 +70,7 @@ def search_bm25( entity_labels: Optional[List[str]] = None, top_k: int = 10, ) -> List[Memory]: - mems = filter_by_namespace(memories, namespace) - mems = filter_by_entity_labels(mems, entity_labels) + mems = _apply_filters(memories, namespace, entity_labels, None) results = bm25_search(query, mems, top_k=top_k) return [m for m, _ in results] @@ -64,7 +82,74 @@ def search_fuzzy( entity_labels: Optional[List[str]] = None, top_k: int = 10, ) -> List[Memory]: - mems = filter_by_namespace(memories, namespace) - mems = filter_by_entity_labels(mems, entity_labels) + mems = _apply_filters(memories, namespace, entity_labels, None) results = fuzzy_search(query, mems, top_k=top_k) - return [m for m, _ in results] \ No newline at end of file + return [m for m, _ in results] + + +def search_vector( + query: str, + memories: List[Memory], + namespace: Optional[str] = None, + entity_labels: Optional[List[str]] = None, + config: Optional[SearchConfig] = None, +) -> List[Memory]: + cfg = config or SearchConfig() + mems = _apply_filters(memories, namespace, entity_labels, cfg) + results = vector_search(query, mems, cfg) + return [m for m, _ in results] + + +def search_regex( + pattern: str, + memories: List[Memory], + namespace: Optional[str] = None, + entity_labels: Optional[List[str]] = None, + flags: int | None = None, + top_k: int = 10, +) -> List[Memory]: + mems = _apply_filters(memories, namespace, entity_labels, None) + regex = re.compile(pattern, flags or re.IGNORECASE) + scored: List[tuple[Memory, int]] = [] + for mem in mems: + haystacks = [mem.name] + [str(value) for value in mem.metadata.values()] + matches = [len(regex.findall(h)) for h in haystacks] + score = max(matches, default=0) + if score > 0: + scored.append((mem, score)) + scored.sort(key=lambda item: item[1], reverse=True) + return [mem for mem, _ in scored[:top_k]] + + +def search_exact( + query: str, + memories: List[Memory], + namespace: Optional[str] = None, + entity_labels: Optional[List[str]] = None, + fields: Optional[List[str]] = None, + case_sensitive: bool = False, + top_k: int = 10, +) -> List[Memory]: + mems = _apply_filters(memories, namespace, entity_labels, None) + needle = query if case_sensitive else query.lower() + fields = fields or ["name"] + + def normalize(value: object) -> str: + text = "" if value is None else str(value) + return text if case_sensitive else text.lower() + + matched: List[Memory] = [] + for mem in mems: + for field in fields: + if field == "metadata": + metadata = getattr(mem, "metadata", {}) + if isinstance(metadata, dict): + if any(normalize(meta_val) == needle for meta_val in metadata.values()): + matched.append(mem) + break + continue + value = getattr(mem, field, None) + if normalize(value) == needle: + matched.append(mem) + break + return matched[:top_k] diff --git a/meshmind/retrieval/vector.py b/meshmind/retrieval/vector.py new file mode 100644 index 0000000..2ca83a6 --- /dev/null +++ b/meshmind/retrieval/vector.py @@ -0,0 +1,57 @@ +"""Vector-only retrieval helpers.""" +from __future__ import annotations + +from typing import Iterable, List, Sequence, Tuple + +from meshmind.core.embeddings import EncoderRegistry +from meshmind.core.similarity import cosine_similarity +from meshmind.core.types import Memory, SearchConfig + + +def vector_search( + query: str, + memories: Sequence[Memory], + config: SearchConfig | None = None, +) -> List[Tuple[Memory, float]]: + """Rank memories using cosine similarity against the query embedding.""" + if not memories: + return [] + + cfg = config or SearchConfig() + encoder = EncoderRegistry.get(cfg.encoder) + query_embedding = encoder.encode([query])[0] + + scored: List[Tuple[Memory, float]] = [] + for memory in memories: + embedding = getattr(memory, "embedding", None) + if embedding is None: + continue + try: + score = cosine_similarity(query_embedding, embedding) + except Exception: + score = 0.0 + scored.append((memory, float(score))) + + scored.sort(key=lambda item: item[1], reverse=True) + return scored[: cfg.top_k] + + +def vector_search_from_embeddings( + query_embedding: Sequence[float], + memories: Iterable[Memory], + top_k: int = 10, +) -> List[Tuple[Memory, float]]: + """Rank memories when the query embedding is precomputed.""" + scored: List[Tuple[Memory, float]] = [] + for memory in memories: + embedding = getattr(memory, "embedding", None) + if embedding is None: + continue + try: + score = cosine_similarity(query_embedding, embedding) + except Exception: + score = 0.0 + scored.append((memory, float(score))) + + scored.sort(key=lambda item: item[1], reverse=True) + return scored[:top_k] diff --git a/meshmind/tasks/celery_app.py b/meshmind/tasks/celery_app.py index 988f355..46fe7f9 100644 --- a/meshmind/tasks/celery_app.py +++ b/meshmind/tasks/celery_app.py @@ -36,4 +36,4 @@ def decorator(fn): app.conf.timezone = 'UTC' app.conf.enable_utc = True else: - app = _DummyCeleryApp() \ No newline at end of file + app = _DummyCeleryApp() diff --git a/meshmind/tasks/scheduled.py b/meshmind/tasks/scheduled.py index eaa4ce2..77c145b 100644 --- a/meshmind/tasks/scheduled.py +++ b/meshmind/tasks/scheduled.py @@ -1,6 +1,9 @@ -""" -Scheduled Celery tasks for expiry, consolidation, and compression. -""" +"""Scheduled Celery tasks for expiry, consolidation, and compression.""" +from __future__ import annotations + +import time +from typing import Callable + try: from celery.schedules import crontab _CELERY_BEAT = True @@ -9,25 +12,80 @@ _CELERY_BEAT = False def crontab(*args, **kwargs): # type: ignore return None -from meshmind.tasks.celery_app import app -from meshmind.pipeline.expire import expire_memories -from meshmind.pipeline.consolidate import consolidate_memories -from meshmind.pipeline.compress import compress_memories from meshmind.api.memory_manager import MemoryManager -from meshmind.db.memgraph_driver import MemgraphDriver from meshmind.core.config import settings +from meshmind.core.observability import log_event, telemetry +from meshmind.db.factory import create_graph_driver +from meshmind.pipeline.compress import compress_memories +from meshmind.pipeline.consolidate import ( + ConsolidationOutcome, + ConsolidationPlan, + consolidate_memories, +) +from meshmind.pipeline.expire import expire_memories +from meshmind.tasks.celery_app import app + +_MANAGER: MemoryManager | None = None +_sleep = time.sleep + + +def _compute_backoff_delay(base: float, attempt: int) -> float: + """Return the exponential backoff delay for the given attempt.""" + + base = max(base, 0.0) + if attempt <= 0: + return base + return base * (2**attempt) -# Initialize database driver and memory manager (fallback if mgclient missing) -try: - driver = MemgraphDriver( - settings.MEMGRAPH_URI, - settings.MEMGRAPH_USERNAME, - settings.MEMGRAPH_PASSWORD, - ) - manager = MemoryManager(driver) -except Exception: - driver = None # type: ignore - manager = None # type: ignore + +def _run_with_retry(operation: Callable[[], None], *, description: str) -> None: + """Execute ``operation`` with retry/backoff semantics for maintenance writes.""" + + max_attempts = max(int(getattr(settings, "MAINTENANCE_MAX_ATTEMPTS", 1)), 1) + base_delay = float(getattr(settings, "MAINTENANCE_BASE_DELAY_SECONDS", 0.0)) + attempt = 0 + while True: + try: + operation() + if attempt: + telemetry.increment("task.maintenance.retry_success") + return + except Exception as exc: # pragma: no cover - specific backend errors vary + attempt += 1 + telemetry.increment("task.maintenance.retries") + log_event( + "task.maintenance.retry", + operation=description, + attempt=attempt, + error=str(exc), + ) + if attempt >= max_attempts: + telemetry.increment("task.maintenance.failures") + raise + delay = _compute_backoff_delay(base_delay, attempt - 1) + telemetry.observe("task.maintenance.backoff_seconds", delay) + if delay > 0: + _sleep(delay) + + +def _reset_manager() -> None: + global _MANAGER + _MANAGER = None + + +def _get_manager() -> MemoryManager | None: + global _MANAGER + if _MANAGER is not None: + return _MANAGER + + try: + driver = create_graph_driver() + except Exception as exc: + log_event("task.manager.error", error=str(exc)) + return None + + _MANAGER = MemoryManager(driver) + return _MANAGER # Define periodic task schedule if Celery is available if _CELERY_BEAT and hasattr(app, 'conf'): @@ -50,30 +108,93 @@ def crontab(*args, **kwargs): # type: ignore @app.task(name='meshmind.tasks.scheduled.expire_task') def expire_task(): """Delete expired memories based on TTL.""" + manager = _get_manager() if manager is None: return [] - return expire_memories(manager) + log_event("task.expire.start") + with telemetry.track_duration("task.expire.duration"): + results = expire_memories(manager) + telemetry.increment("task.expire.runs") + log_event("task.expire.complete", removed=len(results)) + return results @app.task(name='meshmind.tasks.scheduled.consolidate_task') def consolidate_task(): """Merge duplicate memories and summarise.""" + manager = _get_manager() if manager is None: return 0 memories = manager.list_memories() - consolidated = consolidate_memories(memories) - for mem in consolidated: - manager.update_memory(mem) - return len(consolidated) + log_event("task.consolidate.start", memories=len(memories)) + merged_count = 0 + removed_total = 0 + failed_batches = 0 + with telemetry.track_duration("task.consolidate.duration"): + plan = consolidate_memories(memories) + if isinstance(plan, ConsolidationPlan): + skipped = dict(plan.skipped_groups) + else: # backward compatibility + skipped = {} + for outcome in plan: + try: + _apply_consolidation(manager, outcome) + except Exception as exc: # pragma: no cover - depends on backend errors + failed_batches += 1 + log_event( + "task.consolidate.failure", + error=str(exc), + removed=len(outcome.removed_ids), + ) + continue + merged_count += 1 + removed_total += len(outcome.removed_ids) + telemetry.increment("task.consolidate.runs") + telemetry.gauge("task.consolidate.skipped_groups", float(len(skipped))) + telemetry.gauge("task.consolidate.failed_batches", float(failed_batches)) + log_event( + "task.consolidate.complete", + merged=merged_count, + removed=removed_total, + skipped=skipped, + failed=failed_batches, + ) + return { + "merged": merged_count, + "removed": removed_total, + "skipped": skipped, + "failures": failed_batches, + } @app.task(name='meshmind.tasks.scheduled.compress_task') def compress_task(): """Compress long memories to respect token limits.""" + manager = _get_manager() if manager is None: return 0 memories = manager.list_memories() - compressed = compress_memories(memories) - for mem in compressed: - manager.update_memory(mem) - return len(compressed) \ No newline at end of file + log_event("task.compress.start", memories=len(memories)) + updated = 0 + with telemetry.track_duration("task.compress.duration"): + compressed = compress_memories(memories) + for mem in compressed: + manager.update_memory(mem) + updated += 1 + telemetry.increment("task.compress.runs") + log_event("task.compress.complete", updated=updated) + return updated + + +def _apply_consolidation(manager: MemoryManager, outcome: ConsolidationOutcome) -> None: + _run_with_retry( + lambda: manager.update_memory(outcome.updated), + description="update_memory", + ) + for uid in outcome.removed_ids: + if not uid: + continue + _run_with_retry( + lambda value=uid: manager.delete_memory(value), + description="delete_memory", + ) diff --git a/meshmind/testing/__init__.py b/meshmind/testing/__init__.py new file mode 100644 index 0000000..8d9da90 --- /dev/null +++ b/meshmind/testing/__init__.py @@ -0,0 +1,14 @@ +"""Testing utilities and doubles for MeshMind.""" +from .fakes import ( + FakeEmbeddingEncoder, + FakeLLMClient, + FakeMemgraphDriver, + FakeRedisBroker, +) + +__all__ = [ + "FakeEmbeddingEncoder", + "FakeLLMClient", + "FakeMemgraphDriver", + "FakeRedisBroker", +] diff --git a/meshmind/testing/fakes.py b/meshmind/testing/fakes.py new file mode 100644 index 0000000..9620dcc --- /dev/null +++ b/meshmind/testing/fakes.py @@ -0,0 +1,237 @@ +"""Testing doubles for MeshMind components and external services.""" +from __future__ import annotations + +import json +from collections import defaultdict, deque +from types import SimpleNamespace +from typing import Any, Deque, Dict, Iterable, List, Optional + +from meshmind.db.in_memory_driver import InMemoryGraphDriver + + +class FakeMemgraphDriver(InMemoryGraphDriver): + """In-memory substitute that records Cypher interactions for assertions.""" + + def __init__(self) -> None: + super().__init__() + self.cypher_calls: List[tuple[str, Dict[str, Any]]] = [] + + def find(self, cypher: str, params: Dict[str, Any]) -> List[Dict[str, Any]]: + self.cypher_calls.append((cypher, dict(params))) + return super().find(cypher, params) + + +class FakeRedisBroker: + """Lightweight Redis replacement implementing the few operations we use.""" + + def __init__(self) -> None: + self._kv: Dict[str, Any] = {} + self._lists: Dict[str, Deque[Any]] = defaultdict(deque) + self._pubsub: Dict[str, List[Any]] = defaultdict(list) + + # Key/value helpers ------------------------------------------------- + def get(self, key: str) -> Any: + return self._kv.get(key) + + def set(self, key: str, value: Any, ex: Optional[int] = None) -> None: # noqa: ARG002 - expiry unused + self._kv[key] = value + + def delete(self, *keys: str) -> int: + removed = 0 + for key in keys: + if key in self._kv: + del self._kv[key] + removed += 1 + if key in self._lists: + del self._lists[key] + removed += 1 + return removed + + # List helpers ------------------------------------------------------ + def lpush(self, key: str, *values: Any) -> int: + lst = self._lists[key] + for value in values: + lst.appendleft(value) + return len(lst) + + def rpush(self, key: str, *values: Any) -> int: + lst = self._lists[key] + for value in values: + lst.append(value) + return len(lst) + + def lrange(self, key: str, start: int, stop: int) -> List[Any]: + lst = list(self._lists[key]) + if stop == -1: + stop = len(lst) + return lst[start:stop + 1] + + # Pub/Sub helpers --------------------------------------------------- + def publish(self, channel: str, message: Any) -> int: + self._pubsub[channel].append(message) + return len(self._pubsub[channel]) + + +class FakeEmbeddingEncoder: + """Deterministic encoder that hashes text into simple float vectors.""" + + def __init__(self, scale: float = 1.0) -> None: + self.scale = scale + + def encode(self, texts: Iterable[str] | str) -> List[List[float]]: + if isinstance(texts, str): + texts = [texts] + vectors: List[List[float]] = [] + for text in texts: + total = sum(ord(ch) for ch in text) + vectors.append([self.scale * (total % 101) / 100.0]) + return vectors + + +class FakeLLMClient: + """OpenAI-compatible stub that records override usage during tests.""" + + class _Proxy: + def __init__(self, parent: "FakeLLMClient", interface: str) -> None: + self._parent = parent + self._interface = interface + + def create( + self, + *, + operation: str | None = None, + model: str | None = None, + base_url: str | None = None, + **kwargs: Any, + ) -> Any: + return self._parent._create( + self._interface, + operation or ("embedding" if self._interface == "embeddings" else "extraction"), + model, + base_url, + **kwargs, + ) + + def __init__( + self, + *, + models: Dict[str, str] | None = None, + base_urls: Dict[str, Optional[str]] | None = None, + api_key: str | None = None, + call_log: List[Dict[str, Any]] | None = None, + ) -> None: + self._models: Dict[str, str] = {"default": "gpt-5-nano"} + if models: + for key, value in models.items(): + if value: + self._models[key] = value + self._base_urls: Dict[str, Optional[str]] = {"default": None} + if base_urls: + for key, value in base_urls.items(): + self._base_urls[key] = value + self.api_key = api_key or "" + self.calls: List[Dict[str, Any]] = call_log if call_log is not None else [] + self.last_override: Dict[str, Any] | None = None + self.config = SimpleNamespace( + model_for=self._model_for, + base_url_for=self._base_url_for, + ) + self.responses = FakeLLMClient._Proxy(self, "responses") + self.embeddings = FakeLLMClient._Proxy(self, "embeddings") + + # ------------------------------------------------------------------ + # Helpers + # ------------------------------------------------------------------ + def _model_for(self, operation: str, fallback: str | None = None) -> str: + return ( + self._models.get(operation) + or self._models.get("default") + or fallback + or "gpt-5-nano" + ) + + def _base_url_for(self, operation: str) -> Optional[str]: + return self._base_urls.get(operation) or self._base_urls.get("default") + + def with_overrides( + self, + *, + models: Dict[str, Optional[str]] | None = None, + base_urls: Dict[str, Optional[str]] | None = None, + api_key: str | None = None, + ) -> "FakeLLMClient": + new_models = dict(self._models) + if models: + for key, value in models.items(): + if value: + new_models[key] = value + new_base_urls = dict(self._base_urls) + if base_urls: + for key, value in base_urls.items(): + new_base_urls[key] = value + override_details = { + "models": models, + "base_urls": base_urls, + "api_key": api_key, + } + child = FakeLLMClient( + models=new_models, + base_urls=new_base_urls, + api_key=api_key or self.api_key, + call_log=self.calls, + ) + self.last_override = override_details + child.last_override = override_details + return child + + def _create( + self, + interface: str, + operation: str, + model: str | None, + base_url: str | None, + **kwargs: Any, + ) -> Any: + resolved_model = model or self._model_for(operation) + resolved_base = base_url if base_url not in ("", None) else self._base_url_for(operation) + payload = { + "interface": interface, + "operation": operation, + "model": resolved_model, + "base_url": resolved_base, + "kwargs": kwargs, + } + self.calls.append(payload) + + # Construct a synthetic response compatible with llm_rerank expectations. + order = [] + inputs = kwargs.get("input", []) + if inputs: + first = inputs[0] + content = first.get("content") if isinstance(first, dict) else None + if isinstance(content, str): + lines = content.splitlines() + elif isinstance(content, list): + lines = [part.get("text", "") for part in content if isinstance(part, dict)] + else: + lines = [] + for line in lines: + prefix = line.split(":", 1)[0] + if prefix.isdigit(): + order.append(int(prefix)) + if not order: + candidate_count = kwargs.get("candidate_count", 0) + if candidate_count: + order = list(range(candidate_count)) + else: + order = [0] + + response_text = json.dumps({"order": order}) + return SimpleNamespace( + output=[ + SimpleNamespace( + content=[SimpleNamespace(text=response_text)] + ) + ] + ) + diff --git a/meshmind/tests/conftest.py b/meshmind/tests/conftest.py new file mode 100644 index 0000000..06848fc --- /dev/null +++ b/meshmind/tests/conftest.py @@ -0,0 +1,62 @@ +import pytest + +from meshmind.api.memory_manager import MemoryManager +from meshmind.api.service import MemoryService +from meshmind.core.embeddings import EncoderRegistry +from meshmind.core.types import Memory +from meshmind.db.in_memory_driver import InMemoryGraphDriver +from meshmind.testing import FakeLLMClient, FakeMemgraphDriver, FakeRedisBroker + + +@pytest.fixture +def memory_factory(): + def _factory(name: str, **overrides): + payload = {"namespace": "ns", "name": name, "entity_label": "Test"} + payload.update(overrides) + return Memory(**payload) + + return _factory + + +@pytest.fixture +def dummy_encoder(): + EncoderRegistry.clear() + + class _Encoder: + def encode(self, texts): + return [[1.0 if "apple" in text else 0.0] for text in texts] + + name = "dummy-encoder" + EncoderRegistry.register(name, _Encoder()) + yield name + EncoderRegistry.clear() + + +@pytest.fixture +def fake_memgraph_driver(): + return FakeMemgraphDriver() + + +@pytest.fixture +def fake_redis(): + return FakeRedisBroker() + + +@pytest.fixture +def in_memory_driver(): + return InMemoryGraphDriver() + + +@pytest.fixture +def memory_manager(in_memory_driver): + return MemoryManager(in_memory_driver) + + +@pytest.fixture +def fake_llm_client(): + return FakeLLMClient() + + +@pytest.fixture +def memory_service(memory_manager, fake_llm_client): + return MemoryService(memory_manager, llm_client=fake_llm_client) diff --git a/meshmind/tests/docker/full-stack.yml b/meshmind/tests/docker/full-stack.yml new file mode 100644 index 0000000..2b396b2 --- /dev/null +++ b/meshmind/tests/docker/full-stack.yml @@ -0,0 +1,49 @@ +version: "3.9" + +services: + memgraph: + extends: + file: ./memgraph.yml + service: memgraph + container_name: meshmind-int-memgraph + ports: + - "27687:7687" + - "23000:3000" + + neo4j: + extends: + file: ./neo4j.yml + service: neo4j + container_name: meshmind-int-neo4j + ports: + - "27474:7474" + - "27688:7687" + + redis: + extends: + file: ./redis.yml + service: redis + container_name: meshmind-int-redis + ports: + - "26379:6379" + + celery-worker: + build: + context: ../../.. + command: celery -A meshmind.tasks.celery_app worker -B -l info + environment: + MEMGRAPH_URI: bolt://memgraph:7687 + REDIS_URL: redis://redis:6379/0 + PYTHONUNBUFFERED: "1" + depends_on: + memgraph: + condition: service_healthy + redis: + condition: service_healthy + volumes: + - ../../..:/app + working_dir: /app + +networks: + default: + name: meshmind-integration diff --git a/meshmind/tests/docker/memgraph.yml b/meshmind/tests/docker/memgraph.yml new file mode 100644 index 0000000..d9d250e --- /dev/null +++ b/meshmind/tests/docker/memgraph.yml @@ -0,0 +1,22 @@ +version: "3.9" + +services: + memgraph: + image: memgraph/memgraph-platform:latest + container_name: meshmind-test-memgraph + restart: unless-stopped + ports: + - "17687:7687" + - "13000:3000" + environment: + MEMGRAPH_MEMORY_LIMIT: 1GB + healthcheck: + test: ["CMD", "bash", "-c", "cypher-shell --version || exit 1"] + interval: 15s + timeout: 10s + retries: 5 + volumes: + - memgraph-test-data:/var/lib/memgraph + +volumes: + memgraph-test-data: diff --git a/meshmind/tests/docker/neo4j.yml b/meshmind/tests/docker/neo4j.yml new file mode 100644 index 0000000..d38bf73 --- /dev/null +++ b/meshmind/tests/docker/neo4j.yml @@ -0,0 +1,28 @@ +version: "3.9" + +services: + neo4j: + image: neo4j:5.26 + container_name: meshmind-test-neo4j + restart: unless-stopped + ports: + - "17474:7474" + - "17688:7687" + environment: + NEO4J_AUTH: neo4j/meshminD123 + NEO4J_PLUGINS: '["apoc"]' + NEO4J_dbms_security_procedures_unrestricted: apoc.* + NEO4J_apoc_import_file_enabled: "true" + NEO4J_apoc_export_file_enabled: "true" + healthcheck: + test: ["CMD", "cypher-shell", "-u", "neo4j", "-p", "meshminD123", "RETURN 1"] + interval: 15s + timeout: 10s + retries: 5 + volumes: + - neo4j-test-data:/data + - neo4j-test-logs:/logs + +volumes: + neo4j-test-data: + neo4j-test-logs: diff --git a/meshmind/tests/docker/redis.yml b/meshmind/tests/docker/redis.yml new file mode 100644 index 0000000..2466d99 --- /dev/null +++ b/meshmind/tests/docker/redis.yml @@ -0,0 +1,20 @@ +version: "3.9" + +services: + redis: + image: redis:7-alpine + container_name: meshmind-test-redis + restart: unless-stopped + ports: + - "16379:6379" + command: redis-server --save 60 1000 --loglevel warning + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + volumes: + - redis-test-data:/data + +volumes: + redis-test-data: diff --git a/meshmind/tests/test_api_examples.py b/meshmind/tests/test_api_examples.py new file mode 100644 index 0000000..0751011 --- /dev/null +++ b/meshmind/tests/test_api_examples.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from fastapi.testclient import TestClient + +from meshmind.api.grpc import GrpcServiceStub +from meshmind.api.memory_manager import MemoryManager +from meshmind.api.rest import create_app +from meshmind.api.service import MemoryPayload, MemoryService +from meshmind.db.in_memory_driver import InMemoryGraphDriver +from meshmind.core.embeddings import EncoderRegistry +from meshmind.protos import memory_service_pb2 as pb2 + + +def _service_with_sample_data() -> MemoryService: + driver = InMemoryGraphDriver() + manager = MemoryManager(driver) + service = MemoryService(manager) + if not EncoderRegistry.is_registered("text-embedding-3-small"): + class _DummyEncoder: + def encode(self, texts): + return [[0.0, 0.0, 0.0] for _ in texts] + + EncoderRegistry.register("text-embedding-3-small", _DummyEncoder()) + payload = MemoryPayload( + namespace="demo", + name="MeshMind architecture overview", + entity_label="Memory", + metadata={ + "content": "MeshMind combines hybrid retrieval with graph persistence.", + "summary": "Architecture overview", + }, + importance=1.2, + ) + service.ingest_memories([payload]) + return service + + +def test_rest_curl_example_matches_service() -> None: + service = _service_with_sample_data() + app = create_app(service) + client = TestClient(app) + + search_payload = { + "query": "architecture", + "namespace": "demo", + "top_k": 5, + "entity_labels": ["Memory"], + } + response = client.post("/search", json=search_payload) + data = response.json() + assert response.status_code == 200 + assert data["results"] + + counts_response = client.get("/memories/counts", params={"namespace": "demo"}) + assert counts_response.status_code == 200 + counts = counts_response.json()["counts"] + assert counts["demo"]["Memory"] >= 1 + + +def test_grpcurl_example_matches_stub() -> None: + service = _service_with_sample_data() + stub = GrpcServiceStub(service) + + request = pb2.SearchPayload(query="architecture", namespace="demo", top_k=5) + response = stub.Search(request) + assert response.results + + counts_request = pb2.MemoryCountsRequest(namespace="demo") + counts_response = stub.MemoryCounts(counts_request) + assert counts_response.counts["demo"].entity_counts["Memory"] >= 1 diff --git a/meshmind/tests/test_benchmark_scripts.py b/meshmind/tests/test_benchmark_scripts.py new file mode 100644 index 0000000..0462863 --- /dev/null +++ b/meshmind/tests/test_benchmark_scripts.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + + +def _repo_root() -> Path: + return Path(__file__).resolve().parents[2] + + +def _run_script(script: str, *args: str) -> dict[str, float]: + repo_root = _repo_root() + script_path = repo_root / "scripts" / script + result = subprocess.run( + [sys.executable, str(script_path), *args], + cwd=repo_root, + capture_output=True, + text=True, + check=True, + ) + return json.loads(result.stdout) + + +def test_evaluate_importance_synthetic() -> None: + data = _run_script("evaluate_importance.py", "--synthetic-count", "5") + assert data["count"] >= 5.0 + assert data["mean"] >= 0.0 + + +def test_consolidation_benchmark_quick_run() -> None: + metrics = _run_script( + "consolidation_benchmark.py", + "--iterations", + "2", + "--namespaces", + "1", + "--groups", + "2", + "--duplicates", + "2", + ) + assert metrics["groups"] == 2.0 + assert metrics["iterations"] == 2.0 + + +def test_pagination_benchmark_memory_backend() -> None: + metrics = _run_script( + "benchmark_pagination.py", + "--backend", + "memory", + "--count", + "50", + "--page-size", + "10", + "--iterations", + "1", + ) + assert metrics["page_size"] == 10.0 + assert metrics["count"] == 50.0 + assert metrics["fetched"] <= 50.0 diff --git a/meshmind/tests/test_cli_admin.py b/meshmind/tests/test_cli_admin.py new file mode 100644 index 0000000..592950c --- /dev/null +++ b/meshmind/tests/test_cli_admin.py @@ -0,0 +1,107 @@ +from argparse import Namespace +from argparse import Namespace +from io import StringIO + +import pytest + +from meshmind.cli import admin +from meshmind.core.observability import telemetry +from meshmind.models.registry import PredicateRegistry + + +@pytest.fixture(autouse=True) +def reset_registry(): + PredicateRegistry.clear() + telemetry.reset() + yield + PredicateRegistry.clear() + telemetry.reset() + + +def test_handle_predicates_add_list_remove(): + stream = StringIO() + admin.handle_predicates(Namespace(add="LIKES", remove=None, list=False), stream=stream) + assert "LIKES" in stream.getvalue() + stream = StringIO() + admin.handle_predicates(Namespace(add=None, remove="LIKES", list=False), stream=stream) + assert "Removed" in stream.getvalue() + stream = StringIO() + admin.handle_predicates(Namespace(add=None, remove=None, list=True), stream=stream) + assert "predicates" in stream.getvalue() + + +def test_handle_maintenance_outputs_snapshot(): + telemetry.increment("events.test") + stream = StringIO() + admin.handle_maintenance( + Namespace(reset=False, max_attempts=None, base_delay=None, run=None), + stream=stream, + ) + output = stream.getvalue() + assert "events.test" in output + + +def test_handle_maintenance_overrides_and_runs(monkeypatch): + original_max = admin.settings.MAINTENANCE_MAX_ATTEMPTS + original_delay = admin.settings.MAINTENANCE_BASE_DELAY_SECONDS + calls: list[str] = [] + + def fake_consolidate(): + calls.append("consolidate") + return {"merged": 1} + + monkeypatch.setattr( + "meshmind.tasks.scheduled.consolidate_task", fake_consolidate + ) + + try: + stream = StringIO() + admin.handle_maintenance( + Namespace( + reset=False, + max_attempts=5, + base_delay=2.5, + run="consolidate", + ), + stream=stream, + ) + assert admin.settings.MAINTENANCE_MAX_ATTEMPTS == 5 + assert admin.settings.MAINTENANCE_BASE_DELAY_SECONDS == 2.5 + assert calls == ["consolidate"] + output = stream.getvalue() + assert '"task": "consolidate"' in output + assert "MAINTENANCE_MAX_ATTEMPTS" in output + finally: + admin.settings.MAINTENANCE_MAX_ATTEMPTS = original_max + admin.settings.MAINTENANCE_BASE_DELAY_SECONDS = original_delay + + +def test_handle_graph_check_with_verify(monkeypatch): + class DummyDriver: + def verify_connectivity(self): + return True + + monkeypatch.setattr(admin, "create_graph_driver", lambda **kwargs: DummyDriver()) + stream = StringIO() + status = admin.handle_graph_check(Namespace(backend="neo4j"), stream=stream) + assert status == 0 + assert "ok" in stream.getvalue() + + +def test_handle_counts_outputs_grouped(monkeypatch): + class DummyDriver: + def __init__(self): + self.closed = False + + def count_entities(self, namespace=None): # noqa: ARG002 - testing helper + return {"demo": {"Note": 3}} + + def close(self): + self.closed = True + + monkeypatch.setattr(admin, "create_graph_driver", lambda **kwargs: DummyDriver()) + stream = StringIO() + status = admin.handle_counts(Namespace(backend="memory", namespace=None), stream=stream) + + assert status == 0 + assert "\"Note\": 3" in stream.getvalue() diff --git a/meshmind/tests/test_client.py b/meshmind/tests/test_client.py new file mode 100644 index 0000000..5a9946d --- /dev/null +++ b/meshmind/tests/test_client.py @@ -0,0 +1,41 @@ +from meshmind.api.memory_manager import MemoryManager +from meshmind.client import MeshMind +from meshmind.db.in_memory_driver import InMemoryGraphDriver + + +def test_meshmind_list_memories_forwards_filters(monkeypatch): + driver = InMemoryGraphDriver() + client = MeshMind(llm_client=object(), graph_driver=driver) + + captured: dict[str, object] = {} + + def fake_list_memories(self, namespace=None, entity_labels=None, **kwargs): # noqa: ANN001 + captured["namespace"] = namespace + captured["entity_labels"] = entity_labels + captured["kwargs"] = kwargs + return [] + + monkeypatch.setattr(MemoryManager, "list_memories", fake_list_memories) + + client.list_memories( + namespace="demo", + entity_labels=["Note"], + offset=5, + limit=10, + query="alpha", + use_search=True, + ) + + assert captured["namespace"] == "demo" + assert captured["entity_labels"] == ["Note"] + assert captured["kwargs"] == {"offset": 5, "limit": 10, "query": "alpha", "use_search": True} + + +def test_meshmind_memory_counts_delegates(monkeypatch): + driver = InMemoryGraphDriver() + client = MeshMind(llm_client=object(), graph_driver=driver) + + monkeypatch.setattr(MemoryManager, "count_memories", lambda self, namespace=None: {"ns": {"Note": 1}}) + + counts = client.memory_counts(namespace="ns") + assert counts["ns"]["Note"] == 1 diff --git a/meshmind/tests/test_counts_smoke.py b/meshmind/tests/test_counts_smoke.py new file mode 100644 index 0000000..0dae026 --- /dev/null +++ b/meshmind/tests/test_counts_smoke.py @@ -0,0 +1,52 @@ +import argparse +import json + +import pytest + +from meshmind.api.memory_manager import MemoryManager +from meshmind.api.rest import RestAPIStub +from meshmind.api.service import MemoryService +from meshmind.cli import admin +from meshmind.core.types import Memory +from meshmind.db.in_memory_driver import InMemoryGraphDriver + + +@pytest.fixture() +def populated_service(): + driver = InMemoryGraphDriver() + manager = MemoryManager(driver) + service = MemoryService(manager) + + mem_a = Memory(namespace="docs", name="Alpha", entity_label="Note", metadata={"content": "A"}) + mem_b = Memory(namespace="docs", name="Beta", entity_label="Note", metadata={"content": "B"}) + mem_c = Memory(namespace="support", name="Ticket", entity_label="Case") + + manager.add_memory(mem_a) + manager.add_memory(mem_b) + manager.add_memory(mem_c) + + return driver, service + + +def test_rest_counts_endpoint_returns_totals(populated_service): + driver, service = populated_service + api = RestAPIStub(service) + + response = api.dispatch("GET", "/memories/counts", {"namespace": "docs"}) + + assert "counts" in response + assert response["counts"]["docs"]["Note"] == 2 + assert "support" not in response["counts"] + + +def test_cli_admin_counts_reports_json(populated_service, monkeypatch, capsys): + driver, service = populated_service + monkeypatch.setattr(admin, "create_graph_driver", lambda backend=None: driver) + + args = argparse.Namespace(backend="memory", namespace=None) + exit_code = admin.handle_counts(args) + + assert exit_code == 0 + payload = json.loads(capsys.readouterr().out) + assert payload["docs"]["Note"] == 2 + assert payload["support"]["Case"] == 1 diff --git a/meshmind/tests/test_db_drivers.py b/meshmind/tests/test_db_drivers.py new file mode 100644 index 0000000..0cdc766 --- /dev/null +++ b/meshmind/tests/test_db_drivers.py @@ -0,0 +1,194 @@ +from uuid import uuid4 + +from meshmind.api.memory_manager import MemoryManager +from meshmind.core.types import Memory, Triplet +from meshmind.db.in_memory_driver import InMemoryGraphDriver +from meshmind.db.sqlite_driver import SQLiteGraphDriver +from meshmind.testing import FakeEmbeddingEncoder, FakeMemgraphDriver, FakeRedisBroker + + +def _memory_payload(name: str) -> dict: + return { + "uuid": str(uuid4()), + "namespace": "test", + "name": name, + "entity_label": "Note", + "metadata": {"content": name}, + } + + +def test_in_memory_driver_roundtrip(): + driver = InMemoryGraphDriver() + payload = _memory_payload("alpha") + driver.upsert_entity("Note", payload["name"], payload) + + entity = driver.get_entity(payload["uuid"]) + assert entity and entity["name"] == "alpha" + + triplet = Triplet( + subject=payload["uuid"], + predicate="related_to", + object=str(uuid4()), + namespace="test", + entity_label="Relation", + ) + driver.upsert_edge( + triplet.subject, + triplet.predicate, + triplet.object, + triplet.model_dump(exclude_none=True), + ) + records = driver.list_triplets("test") + assert records and records[0]["predicate"] == "related_to" + + driver.delete(uuid4()) # deleting unknown should not error + driver.delete_triplet(triplet.subject, triplet.predicate, triplet.object) + assert not driver.list_triplets("test") + + driver.delete(payload["uuid"]) + assert driver.get_entity(payload["uuid"]) is None + + +def test_in_memory_driver_pagination_and_search(): + driver = InMemoryGraphDriver() + for idx in range(5): + payload = _memory_payload(f"item-{idx}") + payload["metadata"]["description"] = f"important note {idx}" + driver.upsert_entity("Note", payload["name"], payload) + + limited = driver.list_entities(namespace="test", offset=1, limit=2) + assert len(limited) == 2 + + searched = driver.search_entities(query="important note 3", namespace="test") + assert len(searched) == 1 + assert searched[0]["name"].endswith("3") + + +def test_sqlite_driver_roundtrip(): + driver = SQLiteGraphDriver(":memory:") + payload = _memory_payload("beta") + driver.upsert_entity("Note", payload["name"], payload) + + entity = driver.get_entity(payload["uuid"]) + assert entity and entity["name"] == "beta" + + triplet = Triplet( + subject=payload["uuid"], + predicate="mentions", + object=str(uuid4()), + namespace="test", + entity_label="Relation", + ) + driver.upsert_edge( + triplet.subject, + triplet.predicate, + triplet.object, + triplet.model_dump(exclude_none=True), + ) + records = driver.list_triplets("test") + assert records and records[0]["predicate"] == "mentions" + + driver.delete(payload["uuid"]) + assert driver.get_entity(payload["uuid"]) is None + assert not driver.list_triplets("test") + + +def test_sqlite_list_entities_filters_namespace_and_label(): + driver = SQLiteGraphDriver(":memory:") + note = _memory_payload("note") + task = _memory_payload("task") + task["entity_label"] = "Task" + driver.upsert_entity("Note", note["name"], note) + driver.upsert_entity("Task", task["name"], task) + + filtered = driver.list_entities(namespace="test", entity_labels=["Note"]) + assert len(filtered) == 1 + assert filtered[0]["entity_label"] == "Note" + + everything = driver.list_entities(namespace="test") + assert len(everything) == 2 + + +def test_sqlite_search_entities_and_pagination(): + driver = SQLiteGraphDriver(":memory:") + for idx in range(6): + payload = _memory_payload(f"row-{idx}") + payload["metadata"]["summary"] = "lorem ipsum" + driver.upsert_entity("Note", payload["name"], payload) + + chunk = driver.list_entities(namespace="test", limit=3) + assert len(chunk) == 3 + + second_page = driver.list_entities(namespace="test", offset=3, limit=3) + assert len(second_page) == 3 + + match = driver.search_entities(query="ipsum", namespace="test") + assert len(match) == 6 + + none = driver.search_entities(query="missing", namespace="test", limit=1) + assert none == [] + + +def test_fake_memgraph_driver_behaviour(): + driver = FakeMemgraphDriver() + payload = _memory_payload("gamma") + driver.upsert_entity("Note", payload["name"], payload) + records = driver.find("MATCH (m) RETURN m", {}) + assert records + assert driver.cypher_calls + + +def test_fake_memgraph_entity_label_filtering(): + driver = FakeMemgraphDriver() + note = _memory_payload("alpha") + other = _memory_payload("beta") + other["entity_label"] = "Task" + driver.upsert_entity("Note", note["name"], note) + driver.upsert_entity("Task", other["name"], other) + + filtered = driver.list_entities(namespace="test", entity_labels=["Note"]) + assert len(filtered) == 1 + assert filtered[0]["entity_label"] == "Note" + + +def test_fake_memgraph_counts(): + driver = FakeMemgraphDriver() + alpha = _memory_payload("alpha") + beta = _memory_payload("beta") + beta["entity_label"] = "Task" + driver.upsert_entity("Note", alpha["name"], alpha) + driver.upsert_entity("Task", beta["name"], beta) + + counts = driver.count_entities() + assert counts["test"]["Note"] == 1 + assert counts["test"]["Task"] == 1 + + +def test_memory_manager_count_memories(): + driver = InMemoryGraphDriver() + manager = MemoryManager(driver) + first = _memory_payload("first") + second = _memory_payload("second") + second["entity_label"] = "Task" + manager.add_memory(Memory(**first)) + manager.add_memory(Memory(**second)) + + counts = manager.count_memories() + assert counts["test"]["Note"] == 1 + assert counts["test"]["Task"] == 1 + + +def test_fake_redis_broker_roundtrip(): + broker = FakeRedisBroker() + broker.set("key", "value") + assert broker.get("key") == "value" + broker.lpush("queue", "a", "b") + assert broker.lrange("queue", 0, -1) == ["b", "a"] + broker.publish("events", {"message": "hello"}) + assert broker.delete("key") == 1 + + +def test_fake_embedding_encoder_hashing(): + encoder = FakeEmbeddingEncoder(scale=2.0) + vec = encoder.encode(["alpha"]) + assert isinstance(vec, list) and isinstance(vec[0][0], float) diff --git a/meshmind/tests/test_docs_guard.py b/meshmind/tests/test_docs_guard.py new file mode 100644 index 0000000..aadbbb1 --- /dev/null +++ b/meshmind/tests/test_docs_guard.py @@ -0,0 +1,25 @@ +from scripts.check_docs_sync import check_docs + + +def test_docs_guard_passes_when_docs_cover_changes(monkeypatch): + monkeypatch.setattr("scripts.check_docs_sync._git_diff", lambda base: ["meshmind/api/memory_manager.py", "docs/api.md"]) + assert check_docs("HEAD") == 0 + + +def test_docs_guard_fails_when_docs_missing(monkeypatch): + monkeypatch.setattr("scripts.check_docs_sync._git_diff", lambda base: ["meshmind/core/config.py"]) + result = check_docs("HEAD") + assert result == 1 + + +def test_docs_guard_requires_setup_for_compose(monkeypatch): + monkeypatch.setattr("scripts.check_docs_sync._git_diff", lambda base: ["docker-compose.yml"]) + assert check_docs("HEAD") == 1 + + +def test_docs_guard_passes_when_setup_updated(monkeypatch): + monkeypatch.setattr( + "scripts.check_docs_sync._git_diff", + lambda base: ["docker-compose.yml", "SETUP.md"], + ) + assert check_docs("HEAD") == 0 diff --git a/meshmind/tests/test_driver_factory.py b/meshmind/tests/test_driver_factory.py new file mode 100644 index 0000000..3bc6c62 --- /dev/null +++ b/meshmind/tests/test_driver_factory.py @@ -0,0 +1,30 @@ +import pytest + +from meshmind.db.factory import create_graph_driver, graph_driver_factory +from meshmind.db.in_memory_driver import InMemoryGraphDriver +from meshmind.db.sqlite_driver import SQLiteGraphDriver + + +def test_create_graph_driver_memory(): + driver = create_graph_driver(backend="memory") + assert isinstance(driver, InMemoryGraphDriver) + + +def test_create_graph_driver_sqlite(tmp_path): + path = tmp_path / "graph.db" + driver = create_graph_driver(backend="sqlite", path=str(path)) + try: + assert isinstance(driver, SQLiteGraphDriver) + finally: + driver.close() + + +def test_graph_driver_factory_callable(): + factory = graph_driver_factory(backend="memory") + driver = factory() + assert isinstance(driver, InMemoryGraphDriver) + + +def test_create_graph_driver_invalid_backend(): + with pytest.raises(ValueError): + create_graph_driver(backend="unknown") diff --git a/meshmind/tests/test_graph_retrieval.py b/meshmind/tests/test_graph_retrieval.py new file mode 100644 index 0000000..1696376 --- /dev/null +++ b/meshmind/tests/test_graph_retrieval.py @@ -0,0 +1,121 @@ +from meshmind.api.memory_manager import MemoryManager +from meshmind.core.types import Memory, SearchConfig +from meshmind.db.in_memory_driver import InMemoryGraphDriver +from meshmind.retrieval.graph import ( + graph_exact_search, + graph_hybrid_search, + graph_regex_search, + graph_vector_search, +) + + +class TrackingDriver(InMemoryGraphDriver): + def __init__(self) -> None: + super().__init__() + self.search_calls: list[dict[str, object]] = [] + self.list_calls = 0 + + def search_entities(self, *args, **kwargs): # noqa: ANN002 - passthrough to super + self.search_calls.append(dict(kwargs)) + return super().search_entities(*args, **kwargs) + + def list_entities(self, *args, **kwargs): # noqa: ANN002 - passthrough to super + self.list_calls += 1 + return super().list_entities(*args, **kwargs) + + +def test_graph_hybrid_search_uses_driver(dummy_encoder): + driver = TrackingDriver() + manager = MemoryManager(driver) + memory = Memory( + namespace="ns", + name="Apple Pie", + entity_label="Recipe", + embedding=[1.0], + ) + manager.add_memory(memory) + config = SearchConfig(encoder=dummy_encoder, top_k=1) + + results = graph_hybrid_search("apple", driver, namespace="ns", config=config) + + assert results and results[0].name == "Apple Pie" + assert driver.search_calls + assert driver.search_calls[0]["query"] == "apple" + + +def test_graph_vector_search_filters_namespace(dummy_encoder): + driver = TrackingDriver() + manager = MemoryManager(driver) + include = Memory( + namespace="keep", + name="Keep", + entity_label="Note", + embedding=[1.0], + ) + exclude = Memory( + namespace="skip", + name="Skip", + entity_label="Note", + embedding=[0.0], + ) + manager.add_memory(include) + manager.add_memory(exclude) + config = SearchConfig(encoder=dummy_encoder, top_k=5) + + results = graph_vector_search("keep", driver, namespace="keep", config=config) + + assert len(results) == 1 + assert results[0].name == include.name + + +def test_graph_exact_search_filters_entity_labels(dummy_encoder): + driver = TrackingDriver() + manager = MemoryManager(driver) + keep = Memory( + namespace="ns", + name="Retain", + entity_label="Note", + metadata={"content": "keep me"}, + ) + skip = Memory( + namespace="ns", + name="Skip", + entity_label="Task", + metadata={"content": "ignore"}, + ) + manager.add_memory(keep) + manager.add_memory(skip) + + results = graph_exact_search( + "Retain", + driver, + namespace="ns", + entity_labels=["Note"], + fields=["name"], + top_k=5, + ) + + assert len(results) == 1 + assert results[0].entity_label == "Note" + + +def test_graph_regex_search_falls_back_to_list(dummy_encoder): + driver = TrackingDriver() + manager = MemoryManager(driver) + memory = Memory( + namespace="ns", + name="Alpha", + entity_label="Note", + metadata={"content": "Value"}, + ) + manager.add_memory(memory) + + results = graph_regex_search( + "Alpha", + driver, + namespace="ns", + entity_labels=["Note"], + ) + assert results + assert driver.list_calls >= 1 + assert not driver.search_calls # regex skips driver search diff --git a/meshmind/tests/test_grpc_runtime.py b/meshmind/tests/test_grpc_runtime.py new file mode 100644 index 0000000..d4b70cc --- /dev/null +++ b/meshmind/tests/test_grpc_runtime.py @@ -0,0 +1,80 @@ +"""Runtime gRPC server tests.""" +from __future__ import annotations + +import asyncio +from contextlib import suppress + +import grpc + +from meshmind.api.grpc import memory_to_proto +from meshmind.api.grpc_server import create_server, serve +from meshmind.api.memory_manager import MemoryManager +from meshmind.api.service import MemoryPayload, MemoryService +from meshmind.db.in_memory_driver import InMemoryGraphDriver +from meshmind.protos import memory_service_pb2 as pb2 +from meshmind.protos import memory_service_pb2_grpc as pb2_grpc +from meshmind.core.embeddings import EncoderRegistry + + +def test_create_server_handles_ingest_and_search() -> None: + class _StubEncoder: + def encode(self, texts): + if isinstance(texts, str): + texts = [texts] + return [[1.0] * 3 for _ in texts] + + stub_registered = False + if not EncoderRegistry.is_registered("text-embedding-3-small"): + EncoderRegistry.register("text-embedding-3-small", _StubEncoder()) + stub_registered = True + + async def _run() -> None: + driver = InMemoryGraphDriver() + manager = MemoryManager(driver) + service = MemoryService(manager) + server, port = create_server(service, host="127.0.0.1", port=0) + await server.start() + try: + async with grpc.aio.insecure_channel(f"127.0.0.1:{port}") as channel: + stub = pb2_grpc.MeshMindServiceStub(channel) + memory = MemoryPayload( + namespace="grpc-tests", + name="Alpha", + embedding=[0.1, 0.2, 0.3], + ) + await stub.IngestMemories( + pb2.IngestMemoriesRequest( + memories=[memory_to_proto(memory.to_memory())] + ) + ) + response = await stub.Search( + pb2.SearchPayload(query="Alpha", namespace="grpc-tests") + ) + assert len(response.results) == 1 + assert response.results[0].name == "Alpha" + finally: + await server.stop(0) + + try: + asyncio.run(_run()) + finally: + if stub_registered: + EncoderRegistry._encoders.pop("text-embedding-3-small", None) + + +def test_serve_lifecycle_handles_cancellation() -> None: + async def _run() -> None: + driver = InMemoryGraphDriver() + manager = MemoryManager(driver) + service = MemoryService(manager) + startup = asyncio.Event() + task = asyncio.create_task( + serve(service, host="127.0.0.1", port=0, startup_event=startup) + ) + await asyncio.wait_for(startup.wait(), timeout=2.0) + await asyncio.sleep(0.05) + task.cancel() + with suppress(asyncio.CancelledError): + await task + + asyncio.run(_run()) diff --git a/meshmind/tests/test_llm_client.py b/meshmind/tests/test_llm_client.py new file mode 100644 index 0000000..3fed2f7 --- /dev/null +++ b/meshmind/tests/test_llm_client.py @@ -0,0 +1,54 @@ +from meshmind.llm_client import DEFAULT_MODEL, LLMClient, LLMConfig + + +class DummyResponses: + def __init__(self, store): + self._store = store + + def create(self, **kwargs): + self._store.append(("responses", kwargs)) + return {"kwargs": kwargs} + + +class DummyEmbeddings: + def __init__(self, store): + self._store = store + + def create(self, **kwargs): + self._store.append(("embeddings", kwargs)) + return {"kwargs": kwargs} + + +def test_llm_config_override_models(): + config = LLMConfig(api_key="key") + overridden = config.override(models={"extraction": "custom"}) + assert overridden.model_for("extraction") == "custom" + assert overridden.model_for("rerank") == DEFAULT_MODEL + + +def test_llm_client_applies_operation_defaults(monkeypatch): + calls = [] + + class DummyOpenAI: # pragma: no cover - construction only used in test + def __init__(self, **kwargs): + calls.append(kwargs) + self.responses = DummyResponses(calls) + self.embeddings = DummyEmbeddings(calls) + + monkeypatch.setattr("meshmind.llm_client._OpenAI", DummyOpenAI) + + config = LLMConfig( + api_key="token", + base_urls={"default": None, "extraction": "https://extract"}, + models={"default": DEFAULT_MODEL, "embedding": "embed-model"}, + ) + client = LLMClient(config) + client.responses.create(messages=[{"role": "user", "content": "hi"}]) + assert calls[0]["base_url"] == "https://extract" + responses_kwargs = calls[1][1] + assert responses_kwargs["model"] == DEFAULT_MODEL + + client.embeddings.create(input=["text"]) + assert calls[-1][0] == "embeddings" + embeddings_kwargs = calls[-1][1] + assert embeddings_kwargs["model"] == "embed-model" diff --git a/meshmind/tests/test_memgraph_driver.py b/meshmind/tests/test_memgraph_driver.py index 03f7e79..66de8cf 100644 --- a/meshmind/tests/test_memgraph_driver.py +++ b/meshmind/tests/test_memgraph_driver.py @@ -55,8 +55,23 @@ def commit(self): # Test upsert_edge does not raise edge_props = {'rel': 'value'} driver.upsert_edge('id1', 'REL', 'id2', edge_props) + # Test delete_triplet uses predicate sanitisation + driver.delete_triplet('id1', 'REL', 'id2') + assert 'DELETE r' in driver._cursor._last_query + # Test list_triplets returns parsed dicts + driver._cursor.description = [ + ('subject',), + ('predicate',), + ('object',), + ('namespace',), + ('metadata',), + ('reference_time',), + ] + driver._cursor._rows = [(('s',), ('p',), ('o',), ('ns',), ({'k': 'v'},), (None,))] + triplets = driver.list_triplets() + assert triplets and triplets[0]['subject'] == ('s',) # Test vector_search returns list # Use dummy record driver._cursor._rows = [([1.0], {'uuid': 'id1'})] out = driver.vector_search([1.0], top_k=1) - assert isinstance(out, list) \ No newline at end of file + assert isinstance(out, list) diff --git a/meshmind/tests/test_neo4j_driver.py b/meshmind/tests/test_neo4j_driver.py new file mode 100644 index 0000000..20cf3f7 --- /dev/null +++ b/meshmind/tests/test_neo4j_driver.py @@ -0,0 +1,40 @@ +import pytest + +from meshmind.db import neo4j_driver + + +def test_verify_connectivity_uses_driver(monkeypatch): + class DummySession: + def run(self, cypher, **params): + return [] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + return False + + class DummyNeo4jDriver: + def __init__(self, uri, auth=None): + self.uri = uri + self.auth = auth + self.connected = False + + def session(self): + return DummySession() + + def verify_connectivity(self): + self.connected = True + return True + + def close(self): + pass + + class DummyGraphDatabase: + @staticmethod + def driver(uri, auth=None): + return DummyNeo4jDriver(uri, auth) + + monkeypatch.setattr(neo4j_driver, "GraphDatabase", DummyGraphDatabase) + driver = neo4j_driver.Neo4jGraphDriver("bolt://localhost:7687", "neo4j", "pass") + assert driver.verify_connectivity() is True diff --git a/meshmind/tests/test_observability.py b/meshmind/tests/test_observability.py new file mode 100644 index 0000000..f191d7b --- /dev/null +++ b/meshmind/tests/test_observability.py @@ -0,0 +1,24 @@ +import pytest + +from meshmind.core.observability import log_event, telemetry +from meshmind.pipeline.store import store_memories + + +@pytest.fixture(autouse=True) +def reset_telemetry(): + telemetry.reset() + yield + telemetry.reset() + + +def test_log_event_increments_counter(): + log_event("unit.test", value=1) + snapshot = telemetry.snapshot() + assert snapshot["counters"]["events.unit.test"] == 1 + + +def test_store_memories_tracks_metrics(memory_factory, in_memory_driver): + memories = [memory_factory("one"), memory_factory("two")] + store_memories(memories, in_memory_driver) + snapshot = telemetry.snapshot() + assert snapshot["counters"]["pipeline.store.memories.stored"] == 2 diff --git a/meshmind/tests/test_pipeline_extract.py b/meshmind/tests/test_pipeline_extract.py index 6b72966..1707cac 100644 --- a/meshmind/tests/test_pipeline_extract.py +++ b/meshmind/tests/test_pipeline_extract.py @@ -1,11 +1,10 @@ import json + import pytest -import openai from meshmind.client import MeshMind from meshmind.core.types import Memory from meshmind.core.embeddings import EncoderRegistry -from meshmind.db.memgraph_driver import MemgraphDriver class DummyEncoder: @@ -14,39 +13,27 @@ def encode(self, texts): return [[len(text)] for text in texts] -class DummyChoice: - def __init__(self, message): - self.message = message - class DummyResponse: - def __init__(self, arg_json): - func_call = {'arguments': arg_json} - self.choices = [DummyChoice({'function_call': func_call})] + def __init__(self, payload): + self.choices = [type('Choice', (), {'message': payload})] -@pytest.fixture(autouse=True) -def patch_openai(monkeypatch): - """Patch OpenAI client to use DummyChat for responses.""" - class DummyChat: +class DummyLLMClient: + class responses: # type: ignore[assignment] @staticmethod - def create(model, messages, functions, function_call): + def create(**kwargs): + messages = kwargs["messages"] names = [m['content'] for m in messages if m['role'] == 'user'] items = [{'name': n, 'entity_label': 'Memory'} for n in names] arg_json = json.dumps({'memories': items}) - return DummyResponse(arg_json) - class DummyModelClient: - def __init__(self): - self.responses = DummyChat - monkeypatch.setattr(openai, 'OpenAI', lambda *args, **kwargs: DummyModelClient()) - return None + return DummyResponse({'function_call': {'arguments': arg_json}}) def test_extract_memories_basic(tmp_path): # Register dummy encoder + EncoderRegistry.clear() EncoderRegistry.register('text-embedding-3-small', DummyEncoder()) - mm = MeshMind() - # override default llm_client to use dummy - mm.llm_client = openai.OpenAI() + mm = MeshMind(llm_client=DummyLLMClient()) # Run extraction texts = ['alpha', 'beta'] results = mm.extract_memories( @@ -64,16 +51,17 @@ def test_extract_memories_basic(tmp_path): assert mem.embedding == [len(text)] -def test_extract_invalid_label(monkeypatch): - # Monkeypatch to return an entry with invalid label - def bad_create(*args, **kwargs): - arg_json = json.dumps({'memories': [{'name': 'x', 'entity_label': 'Bad'}]}) - return DummyResponse(arg_json) - from openai import OpenAI - llm_client = OpenAI() - monkeypatch.setattr(llm_client.responses, 'create', bad_create) +def test_extract_invalid_label(): + class BadLLMClient: + class responses: # type: ignore[assignment] + @staticmethod + def create(**kwargs): + arg_json = json.dumps({'memories': [{'name': 'x', 'entity_label': 'Bad'}]}) + return DummyResponse({'function_call': {'arguments': arg_json}}) + + EncoderRegistry.clear() EncoderRegistry.register('text-embedding-3-small', DummyEncoder()) - mm = MeshMind(llm_client=llm_client) + mm = MeshMind(llm_client=BadLLMClient()) with pytest.raises(ValueError) as e: mm.extract_memories( instructions='Extract:', @@ -81,4 +69,4 @@ def bad_create(*args, **kwargs): entity_types=[Memory], content=['x'], ) - assert 'Invalid entity_label' in str(e.value) \ No newline at end of file + assert 'Invalid entity_label' in str(e.value) diff --git a/meshmind/tests/test_pipeline_maintenance.py b/meshmind/tests/test_pipeline_maintenance.py index fccb78d..5945af8 100644 --- a/meshmind/tests/test_pipeline_maintenance.py +++ b/meshmind/tests/test_pipeline_maintenance.py @@ -1,71 +1,68 @@ -try: - import pytest -except ImportError: - # Define fallback for pytest.approx - class pytest: - @staticmethod - def approx(x): - return x -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone + +import pytest -from meshmind.pipeline.expire import expire_memories -from meshmind.pipeline.consolidate import consolidate_memories -from meshmind.pipeline.compress import compress_memories from meshmind.core.types import Memory +from meshmind.pipeline.consolidate import consolidate_memories +from meshmind.pipeline.expire import expire_memories class DummyManager: def __init__(self, memories): self._memories = memories - self.deleted = [] + self.deleted: list[str] = [] def list_memories(self): - return self._memories + return list(self._memories) def delete_memory(self, memory_id): self.deleted.append(str(memory_id)) - # Methods to satisfy manager interface - def update_memory(self, memory): + def update_memory(self, memory): # pragma: no cover - interface placeholder pass -def make_memory(name, created_at, ttl): +def make_memory(name: str, created_at: datetime, ttl: int | None = None) -> Memory: mem = Memory(namespace="ns", name=name, entity_label="Test") mem.created_at = created_at - mem.reference_time = None mem.ttl_seconds = ttl return mem def test_expire_memories(monkeypatch): - now = datetime(2025, 1, 1, 12, 0, 0) - # Create memories: one expired, one not - m1 = make_memory("a", now - timedelta(seconds=3600), ttl=1800) - m2 = make_memory("b", now - timedelta(seconds=600), ttl=1800) - manager = DummyManager([m1, m2]) - # Monkeypatch datetime.utcnow + now = datetime(2025, 1, 1, 12, 0, 0, tzinfo=timezone.utc) + expired = make_memory("old", now - timedelta(hours=2), ttl=1800) + active = make_memory("fresh", now - timedelta(minutes=5), ttl=1800) + manager = DummyManager([expired, active]) + class DummyDateTime: @classmethod def utcnow(cls): return now - monkeypatch.setattr('meshmind.pipeline.expire.datetime', DummyDateTime) - expired = expire_memories(manager) - assert str(m1.uuid) in expired - assert str(m2.uuid) not in expired - assert str(m1.uuid) in manager.deleted - - -def test_consolidate_memories(): - # Two memories with same name, different importance - m1 = Memory(namespace="ns", name="x", entity_label="Test") - m2 = Memory(namespace="ns", name="x", entity_label="Test") - m1.importance = 0.5 - m2.importance = 1.0 - m3 = Memory(namespace="ns", name="y", entity_label="Test") - result = consolidate_memories([m1, m2, m3]) - # Expect one for 'x' with higher importance and one for 'y' - names = {m.name for m in result} - assert names == {"x", "y"} - selected_x = next(m for m in result if m.name == "x") - assert selected_x.importance == pytest.approx(1.0) + + @classmethod + def now(cls, tz=None): + if tz is None: + return now.replace(tzinfo=None) + return now.astimezone(tz) + + monkeypatch.setattr("meshmind.pipeline.expire.datetime", DummyDateTime) + + removed = expire_memories(manager) + + assert str(expired.uuid) in removed + assert str(active.uuid) not in removed + assert str(expired.uuid) in manager.deleted + + +def test_consolidate_memories_returns_outcomes(): + primary = Memory(namespace="ns", name="Alice", entity_label="Test", metadata={"content": "likes tea"}) + duplicate = Memory(namespace="ns", name="Alice", entity_label="Test", metadata={"content": "likes coffee"}) + primary.importance = 0.8 + duplicate.importance = 0.2 + + plan = consolidate_memories([primary, duplicate]) + assert len(plan.outcomes) == 1 + outcome = plan.outcomes[0] + assert outcome.updated.metadata["consolidated_summary"] + assert outcome.removed_ids diff --git a/meshmind/tests/test_pipeline_preprocess_store.py b/meshmind/tests/test_pipeline_preprocess_store.py index 2d10abd..39d9ec7 100644 --- a/meshmind/tests/test_pipeline_preprocess_store.py +++ b/meshmind/tests/test_pipeline_preprocess_store.py @@ -1,26 +1,67 @@ import pytest -from meshmind.pipeline.preprocess import deduplicate, score_importance, compress -from meshmind.pipeline.store import store_memories +from datetime import datetime, timezone +from random import Random + +from meshmind.pipeline.consolidate import ConsolidationSettings, consolidate_memories +from meshmind.pipeline.preprocess import ( + compress, + deduplicate, + score_importance, +) +from meshmind.pipeline.store import store_memories, store_triplets from meshmind.api.memory_manager import MemoryManager -from meshmind.core.types import Memory +from meshmind.core.types import Memory, Triplet +from meshmind.models.registry import PredicateRegistry +from meshmind.core.observability import telemetry class DummyDriver: def __init__(self): self.entities = [] self.deleted = [] + self.edges = [] + self.deleted_edges = [] def upsert_entity(self, label, name, props): - self.entities.append((label, name, props)) + self.entities.append((label, name, dict(props))) + + def upsert_edge(self, subj, pred, obj, props): + self.edges.append((subj, pred, obj, props)) def delete(self, uuid): self.deleted.append(uuid) + self.entities = [entry for entry in self.entities if str(entry[2].get("uuid")) != str(uuid)] + + def delete_triplet(self, subj, pred, obj): + self.deleted_edges.append((subj, pred, obj)) def find(self, cypher, params): # Return empty for simplicity return [] + def list_triplets(self, namespace=None): + return [] + + def get_entity(self, uuid): + for _, _, props in self.entities: + if str(props.get("uuid")) == str(uuid): + return dict(props) + return None + + def list_entities(self, namespace=None, entity_labels=None, *, offset=0, limit=None): + results = [] + for _, _, props in self.entities: + if namespace is None or props.get("namespace") == namespace: + if entity_labels and props.get("entity_label") not in set(entity_labels): + continue + results.append(dict(props)) + if limit is None: + return results[offset:] + if limit <= 0: + return [] + return results[offset : offset + limit] + def make_memory(name: str) -> Memory: return Memory(namespace="ns", name=name, entity_label="Test") @@ -35,11 +76,31 @@ def test_deduplicate_removes_duplicates(): assert {m.name for m in result} == {"a", "b"} -def test_score_importance_sets_default(): - m = make_memory("x") - m.importance = None - scored = score_importance([m]) - assert scored[0].importance == pytest.approx(1.0) +def test_score_importance_heuristic_variation(): + recent = make_memory("Urgent server outage 500 error") + recent.metadata = {"content": "Service unavailable 500"} + recent.reference_time = datetime.now(timezone.utc) + + mundane = make_memory("Note") + mundane.metadata = {"content": "write unit tests"} + + scored = score_importance([recent, mundane]) + values = [mem.importance for mem in scored] + assert all(val is not None for val in values) + assert scored[0].importance != scored[1].importance + assert max(values) <= 5.0 + + +def test_score_importance_records_metrics(): + telemetry.reset() + m1 = make_memory("Outage 500") + m1.metadata = {"content": "Service returned 500 error"} + m1.reference_time = datetime.now(timezone.utc) + score_importance([m1]) + snapshot = telemetry.snapshot() + gauges = snapshot["gauges"] + assert "importance.mean" in gauges + assert gauges["importance.count"] >= 1.0 def test_compress_noop(): @@ -47,6 +108,59 @@ def test_compress_noop(): assert compress([m])[0] is m +def test_consolidate_memories_merges_duplicates(): + base = make_memory("Alice") + duplicate = make_memory("Alice") + duplicate.metadata = {"content": "Alice likes tea"} + base.metadata = {"content": "Alice likes coffee"} + duplicate.importance = 0.2 + base.importance = 0.9 + + plan = consolidate_memories([base, duplicate]) + assert len(plan.outcomes) == 1 + outcome = plan.outcomes[0] + assert "consolidated_summary" in outcome.updated.metadata + assert outcome.removed_ids + + +def test_consolidate_memories_scales_with_large_dataset(): + rng = Random(42) + memories: list[Memory] = [] + for namespace_idx in range(3): + namespace = f"bulk-{namespace_idx}" + for group_idx in range(10): + base_name = f"Entity {namespace_idx}-{group_idx}" + primary = make_memory(base_name) + primary.namespace = namespace + primary.metadata = {"content": f"{base_name} base description"} + primary.importance = round(rng.random() + 0.5, 3) + memories.append(primary) + for dup_idx in range(7): + duplicate = make_memory(base_name) + duplicate.namespace = namespace + duplicate.metadata = { + "content": f"{base_name} duplicate {dup_idx}", + "summary": f"{base_name} summary {dup_idx}", + } + duplicate.importance = max(primary.importance - 0.1, 0.1) + memories.append(duplicate) + + settings = ConsolidationSettings( + max_group_size=25, + max_updates=400, + max_updates_per_namespace=150, + ) + plan = consolidate_memories(memories, settings=settings) + + assert len(plan.outcomes) == 30 + removed_total = sum(len(outcome.removed_ids) for outcome in plan) + assert removed_total == len(memories) - len(plan.outcomes) + for outcome in plan: + assert outcome.updated.metadata.get("consolidated_summary") + assert outcome.updated.importance is not None + assert all(uid for uid in outcome.removed_ids) + + def test_store_memories_calls_driver(): d = DummyDriver() m1 = make_memory("node1") @@ -57,6 +171,21 @@ def test_store_memories_calls_driver(): assert d.entities[0][1] == "node1" +def test_store_triplets_registers_predicate(): + PredicateRegistry.clear() + d = DummyDriver() + triplet = Triplet( + subject="s", + predicate="RELATES", + object="o", + namespace="ns", + entity_label="Relation", + ) + store_triplets([triplet], d) + assert d.edges and d.edges[0][1] == "RELATES" + assert "RELATES" in PredicateRegistry.all() + + def test_memory_manager_add_update_delete(): d = DummyDriver() mgr = MemoryManager(d) @@ -76,6 +205,24 @@ def test_memory_manager_add_update_delete(): # list returns empty or list lst = mgr.list_memories() assert isinstance(lst, list) + assert mgr.list_memories(entity_labels=["Other"]) == [] + + +def test_memory_manager_triplet_roundtrip(): + d = DummyDriver() + mgr = MemoryManager(d) + triplet = Triplet( + subject="s", + predicate="RELATES", + object="o", + namespace="ns", + entity_label="Relation", + ) + mgr.add_triplet(triplet) + assert d.edges + mgr.delete_triplet(triplet.subject, triplet.predicate, triplet.object) + assert d.deleted_edges + assert mgr.list_triplets() == [] def test_deduplicate_by_embedding_similarity(): # Two memories with similar embeddings should be deduplicated @@ -89,4 +236,4 @@ def test_deduplicate_by_embedding_similarity(): assert len(result_high) == 1 # With low threshold, keep both result_low = deduplicate([m1, m2], threshold=0.1) - assert len(result_low) == 2 \ No newline at end of file + assert len(result_low) == 2 diff --git a/meshmind/tests/test_protos_packaging.py b/meshmind/tests/test_protos_packaging.py new file mode 100644 index 0000000..7d6ea99 --- /dev/null +++ b/meshmind/tests/test_protos_packaging.py @@ -0,0 +1,13 @@ +"""Ensure protobuf assets ship with the package.""" +from __future__ import annotations + +from pathlib import Path + +from meshmind.protos import data_path + + +def test_proto_files_packaged() -> None: + proto_path = Path(data_path("memory_service.proto")) + assert proto_path.exists() + text = proto_path.read_text(encoding="utf-8") + assert "service MeshMindService" in text diff --git a/meshmind/tests/test_retrieval.py b/meshmind/tests/test_retrieval.py index 66abd33..728243e 100644 --- a/meshmind/tests/test_retrieval.py +++ b/meshmind/tests/test_retrieval.py @@ -1,76 +1,127 @@ import pytest -from meshmind.core.types import Memory, SearchConfig -from meshmind.retrieval.bm25 import bm25_search -from meshmind.retrieval.fuzzy import fuzzy_search +from meshmind.client import MeshMind +from meshmind.core.types import SearchConfig +from meshmind.db.in_memory_driver import InMemoryGraphDriver +from meshmind.retrieval import ( + apply_reranker, + llm_rerank, + search, + search_bm25, + search_exact, + search_fuzzy, + search_regex, + search_vector, +) from meshmind.retrieval.hybrid import hybrid_search -from meshmind.retrieval.search import search, search_bm25, search_fuzzy - - -def make_memory(name: str) -> Memory: - return Memory(namespace="ns", name=name, entity_label="Test") - - -@pytest.fixture(autouse=True) -def add_embeddings(): - # Assign dummy embeddings equal to length of name - def _hook(mem: Memory): - mem.embedding = [len(mem.name)] - return mem - Memory.pre_init = _hook - yield - delattr(Memory, 'pre_init') - - -def test_bm25_search(): - docs = [make_memory("apple pie"), make_memory("banana split"), make_memory("cherry tart")] - results = bm25_search("apple", docs, top_k=2) - # Expect 'apple pie' first - assert results and results[0][0].name == "apple pie" - assert results[0][1] > 0 - - -def test_fuzzy_search(): - docs = [make_memory("apple pie"), make_memory("banana split")] - results = fuzzy_search("apple pie", docs, top_k=2) - assert results and results[0][0].name == "apple pie" - assert 0 < results[0][1] <= 1.0 - - -def test_hybrid_search(): - # Setup memories - m1 = make_memory("apple") - m2 = make_memory("banana") - m1.embedding = [1.0] - m2.embedding = [0.0] - docs = [m1, m2] - config = SearchConfig(encoder="dummy", top_k=2, hybrid_weights=(0.5, 0.5)) - # Register dummy encoder that returns [1] for 'apple' and [0] for 'banana' - class DummyEncoder: - def encode(self, texts): - return [[1.0] if "apple" in t else [0.0] for t in texts] - from meshmind.core.embeddings import EncoderRegistry - EncoderRegistry.register("dummy", DummyEncoder()) - results = hybrid_search("apple", docs, config) - # apple should have highest hybrid score - assert results[0][0].name == "apple" - - -def test_search_dispatcher(): - m1 = make_memory("apple") - m2 = make_memory("banana") - m1.embedding = [1.0] - m2.embedding = [0.0] - docs = [m1, m2] - from meshmind.core.embeddings import EncoderRegistry - class DummyEncoder: - def encode(self, texts): return [[1.0] if "apple" in t else [0.0] for t in texts] - EncoderRegistry.register("dummy", DummyEncoder()) - config = SearchConfig(encoder="dummy", top_k=1, hybrid_weights=(0.5,0.5)) - res = search("apple", docs, namespace="ns", entity_labels=["Test"], config=config) - assert len(res) == 1 and res[0].name == "apple" - # BM25 and fuzzy via dispatcher - res2 = search_bm25("banana", docs) - assert res2 and res2[0].name == "banana" - res3 = search_fuzzy("banana", docs) - assert res3 and res3[0].name == "banana" \ No newline at end of file + + +def test_bm25_search(memory_factory): + docs = [ + memory_factory("apple pie"), + memory_factory("banana split"), + memory_factory("cherry tart"), + ] + results = search_bm25("apple", docs, top_k=2) + assert results and results[0].name == "apple pie" + + +def test_fuzzy_search(memory_factory): + docs = [memory_factory("apple pie"), memory_factory("banana split")] + results = search_fuzzy("apple pie", docs, top_k=2) + assert results and results[0].name == "apple pie" + + +def test_hybrid_search(memory_factory, dummy_encoder): + m1 = memory_factory("apple", embedding=[1.0]) + m2 = memory_factory("banana", embedding=[0.0]) + config = SearchConfig(encoder=dummy_encoder, top_k=2, hybrid_weights=(0.5, 0.5)) + ranked = hybrid_search("apple", [m1, m2], config) + assert ranked[0][0].name == "apple" + + +def test_vector_search(memory_factory, dummy_encoder): + m1 = memory_factory("apple", embedding=[1.0]) + m2 = memory_factory("banana", embedding=[0.0]) + config = SearchConfig(encoder=dummy_encoder, top_k=1) + results = search_vector("apple", [m1, m2], config=config) + assert results == [m1] + + +def test_regex_search(memory_factory): + docs = [ + memory_factory("Visit Paris", metadata={"city": "Paris"}), + memory_factory("Visit Berlin", metadata={"city": "Berlin"}), + ] + results = search_regex("paris", docs, top_k=5) + assert len(results) == 1 and results[0].name == "Visit Paris" + + +def test_exact_search(memory_factory): + docs = [ + memory_factory("Python"), + memory_factory("Rust", metadata={"language": "Rust"}), + ] + results = search_exact("rust", docs, fields=["metadata"], case_sensitive=False) + assert results and results[0].name == "Rust" + + +def test_search_dispatcher_with_rerank(memory_factory, dummy_encoder): + m1 = memory_factory("apple", embedding=[1.0]) + m2 = memory_factory("banana", embedding=[0.1]) + docs = [m2, m1] + config = SearchConfig(encoder=dummy_encoder, top_k=2, rerank_k=2) + + class DummyLLM: + class responses: + @staticmethod + def create(**kwargs): + return type( + "Resp", + (), + { + "output": [ + type( + "Out", + (), + { + "content": [ + type("Text", (), {"text": '{"order": [1, 0]}'}) + ] + }, + ) + ] + }, + ) + + reranked = search( + "apple", + docs, + config=config, + reranker=lambda q, c, k: llm_rerank(q, c, DummyLLM(), k, model="dummy"), + ) + assert reranked[0].name == "apple" + + +def test_apply_reranker_default(memory_factory): + docs = [memory_factory("alpha"), memory_factory("beta")] + ranked = apply_reranker("alpha", docs, top_k=1) + assert ranked == [docs[0]] + + +def test_llm_rerank_failure(memory_factory): + docs = [memory_factory("alpha"), memory_factory("beta")] + result = llm_rerank("alpha", docs, llm_client=None, top_k=2) + assert len(result) == 2 + + +def test_client_search_uses_graph_when_memories_none(dummy_encoder, memory_factory): + driver = InMemoryGraphDriver() + client = MeshMind(llm_client=object(), embedding_model=dummy_encoder, graph_driver=driver) + memory = memory_factory("apple", embedding=[1.0]) + client.create_memory(memory) + config = SearchConfig(encoder=dummy_encoder, top_k=1) + + results = client.search("apple", memories=None, namespace="ns", config=config) + + assert results and results[0].name == "apple" diff --git a/meshmind/tests/test_service_interfaces.py b/meshmind/tests/test_service_interfaces.py new file mode 100644 index 0000000..628a647 --- /dev/null +++ b/meshmind/tests/test_service_interfaces.py @@ -0,0 +1,228 @@ +from uuid import UUID + +from meshmind.api.grpc import GrpcServiceStub, memory_to_proto, triplet_to_proto +from meshmind.api.rest import RestAPIStub +from meshmind.api.service import MemoryPayload, SearchPayload, TripletPayload +from meshmind.core.types import Memory +from meshmind.protos import memory_service_pb2 as pb2 + + +def _memory(name: str) -> MemoryPayload: + return MemoryPayload(namespace="test", name=name, entity_label="Note") + + +def test_memory_service_ingest_and_search(memory_service, dummy_encoder): + payloads = [_memory("apple"), _memory("banana")] + uuids = memory_service.ingest_memories(payloads) + assert len(uuids) == 2 + + captured: dict[str, object] = {} + + sample = Memory( + uuid=UUID(uuids[0]), + namespace="test", + name="apple", + entity_label="Note", + content="apple", + ) + + def fake_list(namespace=None, entity_labels=None, **kwargs): # noqa: ANN001 + captured["namespace"] = namespace + captured["entity_labels"] = entity_labels + captured["kwargs"] = kwargs + return [sample] + + original = memory_service.manager.list_memories + memory_service.manager.list_memories = fake_list # type: ignore[assignment] + + request = SearchPayload(query="apple", namespace="test", encoder=dummy_encoder, top_k=1) + results = memory_service.search(request) + memory_service.manager.list_memories = original # restore + + assert results and isinstance(results[0], Memory) + assert captured["namespace"] == "test" + assert captured["entity_labels"] is None or captured["entity_labels"] == [] + assert captured["kwargs"]["query"] == "apple" + assert captured["kwargs"]["use_search"] is True + assert captured["kwargs"]["limit"] is not None + + +def test_rest_stub_routes(memory_service, dummy_encoder): + app = RestAPIStub(memory_service) + response = app.dispatch( + "POST", + "/memories", + {"memories": [_memory("alpha").model_dump()]}, + ) + assert "uuids" in response + + search_response = app.dispatch( + "POST", + "/search", + {"query": "alpha", "namespace": "test", "encoder": dummy_encoder, "top_k": 1}, + ) + assert search_response["results"] + + memory_service.llm_client.calls.clear() + override_response = app.dispatch( + "POST", + "/search", + { + "query": "alpha", + "namespace": "test", + "encoder": dummy_encoder, + "top_k": 1, + "use_llm_rerank": True, + "llm_models": {"rerank": "rerank-dev"}, + "llm_base_urls": {"rerank": "https://llm.example/rerank"}, + "llm_api_key": "override-key", + }, + ) + assert override_response["results"] + assert memory_service.llm_client.calls + last_call = memory_service.llm_client.calls[-1] + assert last_call["model"] == "rerank-dev" + assert last_call["base_url"] == "https://llm.example/rerank" + assert memory_service.llm_client.last_override["models"]["rerank"] == "rerank-dev" + + filtered = app.dispatch( + "GET", + "/memories", + {"namespace": "test", "entity_labels": ["Note"]}, + ) + assert filtered["memories"] + + filtered_none = app.dispatch( + "GET", + "/memories", + {"namespace": "test", "entity_labels": ["Task"]}, + ) + assert filtered_none["memories"] == [] + + counts = app.dispatch("GET", "/memories/counts", {"namespace": "test"}) + assert counts["counts"]["test"]["Note"] >= 1 + + +def test_memory_service_list_memories_forwards_kwargs(memory_service): + captured: dict[str, object] = {} + + def fake_list(namespace=None, entity_labels=None, **kwargs): # noqa: ANN001 + captured["namespace"] = namespace + captured["entity_labels"] = entity_labels + captured["kwargs"] = kwargs + return [] + + original = memory_service.manager.list_memories + memory_service.manager.list_memories = fake_list # type: ignore[assignment] + + memory_service.list_memories( + namespace="demo", + entity_labels=["Note"], + offset=5, + limit=10, + query="python", + use_search=False, + ) + + memory_service.manager.list_memories = original # restore + + assert captured["namespace"] == "demo" + assert captured["entity_labels"] == ["Note"] + assert captured["kwargs"] == {"offset": 5, "limit": 10, "query": "python", "use_search": False} + + +def test_memory_service_search_applies_llm_overrides(memory_service, dummy_encoder): + sample = Memory( + uuid=UUID(int=1), + namespace="test", + name="apple", + entity_label="Note", + content="apple", + ) + + def fake_list(namespace=None, entity_labels=None, **kwargs): # noqa: ANN001 + return [sample] + + original = memory_service.manager.list_memories + memory_service.manager.list_memories = fake_list # type: ignore[assignment] + + payload = SearchPayload( + query="apple", + namespace="test", + encoder=dummy_encoder, + top_k=1, + use_llm_rerank=True, + llm_models={"rerank": "direct-rerank"}, + llm_base_urls={"default": "https://llm.example/default"}, + llm_api_key="override-key", + ) + + memory_service.llm_client.calls.clear() + results = memory_service.search(payload) + memory_service.manager.list_memories = original + + assert results + assert memory_service.llm_client.calls + direct_call = memory_service.llm_client.calls[-1] + assert direct_call["model"] == "direct-rerank" + assert direct_call["base_url"] == "https://llm.example/default" + assert memory_service.llm_client.last_override["base_urls"]["default"] == "https://llm.example/default" + + +def test_grpc_stub(memory_service, dummy_encoder): + grpc = GrpcServiceStub(memory_service) + request = pb2.IngestMemoriesRequest( + memories=[memory_to_proto(_memory("cherry").to_memory())] + ) + resp = grpc.IngestMemories(request) + assert resp.uuids + + search_resp = grpc.Search( + pb2.SearchPayload( + query="cherry", + namespace="test", + top_k=1, + encoder=dummy_encoder, + entity_labels=["Note"], + ) + ) + assert search_resp.results + + memory_service.llm_client.calls.clear() + override_request = pb2.SearchPayload( + query="cherry", + namespace="test", + top_k=1, + encoder=dummy_encoder, + entity_labels=["Note"], + use_llm_rerank=True, + llm_models={"rerank": "rerank-grpc"}, + llm_base_urls={"rerank": "https://llm.example/grpc"}, + llm_api_key="override-key", + ) + override_resp = grpc.Search(override_request) + assert override_resp.results + assert memory_service.llm_client.calls + grpc_call = memory_service.llm_client.calls[-1] + assert grpc_call["model"] == "rerank-grpc" + assert grpc_call["base_url"] == "https://llm.example/grpc" + + counts_resp = grpc.MemoryCounts(pb2.MemoryCountsRequest(namespace="test")) + assert counts_resp.counts["test"].entity_counts["Note"] >= 1 + + triplet_resp = grpc.IngestTriplets( + pb2.IngestTripletsRequest( + triplets=[ + triplet_to_proto( + TripletPayload( + subject=resp.uuids[0], + predicate="linked_to", + object=resp.uuids[0], + namespace="test", + entity_label="Relation", + ).to_triplet() + ) + ] + ) + ) + assert triplet_resp.stored == 1 diff --git a/meshmind/tests/test_setup_scripts.py b/meshmind/tests/test_setup_scripts.py new file mode 100644 index 0000000..0d05725 --- /dev/null +++ b/meshmind/tests/test_setup_scripts.py @@ -0,0 +1,31 @@ +"""Smoke tests for environment provisioning scripts.""" +from __future__ import annotations + +import os +import subprocess +from pathlib import Path + +import pytest + + +@pytest.mark.parametrize("script_name", ["install_setup.sh", "maintenance_setup.sh"]) +def test_setup_scripts_validate_optional_packages(script_name: str) -> None: + repo_root = Path(__file__).resolve().parents[2] + script_path = repo_root / "run" / script_name + env = os.environ.copy() + env.update({ + "MESH_SKIP_SYSTEM_PACKAGES": "1", + "MESH_SKIP_PYTHON_SYNC": "1", + }) + result = subprocess.run( + ["bash", str(script_path)], + cwd=repo_root, + env=env, + capture_output=True, + text=True, + check=True, + ) + stdout = result.stdout + assert "Validated optional packages: fastapi, neo4j, pymgclient" in stdout + assert "Skipping system package installation" in stdout + assert "Skipping Python dependency sync" in stdout diff --git a/meshmind/tests/test_tasks_scheduled.py b/meshmind/tests/test_tasks_scheduled.py new file mode 100644 index 0000000..4e135ec --- /dev/null +++ b/meshmind/tests/test_tasks_scheduled.py @@ -0,0 +1,93 @@ +import pytest + +from meshmind.core.types import Memory +from meshmind.tasks import scheduled + + +class StubManager: + def __init__(self, memories): + self._memories = memories + self.updated = [] + self.deleted = [] + + def list_memories(self): + return list(self._memories) + + def update_memory(self, memory): + self.updated.append(memory) + + def delete_memory(self, memory_id): + self.deleted.append(str(memory_id)) + + +@pytest.fixture(autouse=True) +def reset_manager(): + scheduled._reset_manager() + yield + scheduled._reset_manager() + + +def test_consolidate_task_persists_updates(monkeypatch): + mem_a = Memory(namespace="ns", name="Alice", entity_label="Person", metadata={"content": "Alice likes tea"}) + mem_b = Memory(namespace="ns", name="Alice", entity_label="Person", metadata={"content": "Alice likes coffee"}) + mem_b.importance = 0.9 + + manager = StubManager([mem_a, mem_b]) + monkeypatch.setattr(scheduled, "_MANAGER", manager, raising=False) + + stats = scheduled.consolidate_task() + + assert stats["merged"] == 1 + assert stats["removed"] >= 1 + assert stats["failures"] == 0 + assert manager.updated + assert manager.deleted + + +def test_consolidate_task_retries_conflicts(monkeypatch): + mem_a = Memory(namespace="ns", name="Alice", entity_label="Person", metadata={"content": "tea"}) + mem_b = Memory(namespace="ns", name="Alice", entity_label="Person", metadata={"content": "coffee"}) + mem_b.importance = 0.9 + + manager = StubManager([mem_a, mem_b]) + attempts = {"count": 0} + + def flaky_update(memory): + attempts["count"] += 1 + if attempts["count"] < 3: + raise RuntimeError("conflict") + manager.updated.append(memory) + + monkeypatch.setattr(manager, "update_memory", flaky_update, raising=False) + monkeypatch.setattr(scheduled, "_MANAGER", manager, raising=False) + monkeypatch.setattr(scheduled.settings, "MAINTENANCE_MAX_ATTEMPTS", 3, raising=False) + monkeypatch.setattr(scheduled.settings, "MAINTENANCE_BASE_DELAY_SECONDS", 0.0, raising=False) + monkeypatch.setattr(scheduled, "_sleep", lambda *_: None, raising=False) + + stats = scheduled.consolidate_task() + + assert attempts["count"] == 3 + assert stats["failures"] == 0 + assert manager.deleted + + +def test_consolidate_task_reports_failures(monkeypatch): + mem_a = Memory(namespace="ns", name="Alice", entity_label="Person", metadata={"content": "tea"}) + mem_b = Memory(namespace="ns", name="Alice", entity_label="Person", metadata={"content": "coffee"}) + mem_b.importance = 0.9 + + manager = StubManager([mem_a, mem_b]) + + def failing_update(memory): + raise RuntimeError("conflict") + + monkeypatch.setattr(manager, "update_memory", failing_update, raising=False) + monkeypatch.setattr(scheduled, "_MANAGER", manager, raising=False) + monkeypatch.setattr(scheduled.settings, "MAINTENANCE_MAX_ATTEMPTS", 2, raising=False) + monkeypatch.setattr(scheduled.settings, "MAINTENANCE_BASE_DELAY_SECONDS", 0.0, raising=False) + monkeypatch.setattr(scheduled, "_sleep", lambda *_: None, raising=False) + + stats = scheduled.consolidate_task() + + assert stats["failures"] == 1 + assert manager.deleted == [] diff --git a/pyproject.toml b/pyproject.toml index 062f6d7..974d6a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "meshmind" version = "0.1.0" description = "AI Agent Memory management system using LLMs and graph databases" readme = "README.md" -requires-python = ">=3.13" +requires-python = ">=3.11,<3.13" dependencies = [ "openai>=1.78.0", "pydantic>=2.11.4", @@ -17,8 +17,44 @@ dependencies = [ "sentence-transformers>=4.1.0", "typing-extensions>=4.13.2", "tiktoken>=0.9.0", + "fastapi>=0.115.6", + "uvicorn[standard]>=0.34.0", + "neo4j>=5.26.0", "pymgclient>=1.4.0", + "redis>=5.0.0", + "grpcio>=1.67.0", + "grpcio-tools>=1.67.0", + "protobuf>=5.29.2", ] [project.scripts] meshmind = "meshmind.cli.__main__:main" + +[project.optional-dependencies] +dev = [ + "ruff>=0.7.3", + "pyright>=1.1.386", + "typeguard>=4.3.0", + "toml-sort>=0.23.1", + "yamllint>=1.35.1", +] +docs = [ + "mkdocs>=1.6.1", + "mkdocs-material>=9.5.39", +] +testing = [ + "pytest-cov>=5.0.0", + "httpx>=0.28.1", + "fastapi>=0.115.0", + "uvicorn>=0.32.0", + "neo4j>=5.25.0", + "pymgclient>=1.5.1", + "grpcio>=1.67.0", + "grpcio-tools>=1.67.0", + "protobuf>=5.29.2", +] + +[tool.setuptools.package-data] +"meshmind" = [ + "protos/*.proto", +] diff --git a/research/overview.md b/research/overview.md new file mode 100644 index 0000000..edae9d7 --- /dev/null +++ b/research/overview.md @@ -0,0 +1,19 @@ +# Competitive Landscape + +## OpenAI Ecosystem +- **OpenAI GPT-5 Nano / Assistants** – Reference implementation for RAG orchestration. Strengths include turnkey hosting and builtin evaluation tools, but tight coupling to OpenAI pricing and rate limits motivates MeshMind's provider-agnostic design. + +## Vector Database Platforms +- **Pinecone** – Specialised for vector search with managed infrastructure and hybrid scoring. Highlights the need for backend-native vector similarity and metrics around recall/latency. +- **Weaviate** – Offers hybrid vector/keyword search and schema management. Inspires MeshMind's emphasis on namespace/entity-label governance and plugin-style encoders. +- **Milvus** – Open-source large-scale vector store with distributed clustering. Underscores the importance of benchmarking persistence choices as scale grows. + +## Knowledge Bases & Productivity Tools +- **Mem** – Focuses on personal knowledge capture with AI summarisation. Validates MeshMind's consolidation heuristics and highlights opportunities for human-in-the-loop editing. +- **Notion Q&A / Confluence AI** – Embed RAG across collaborative docs. Demonstrates demand for admin observability (usage metrics, access controls) that MeshMind plans to deliver. + +## Takeaways for MeshMind +- Prioritise backend flexibility: keep graph driver interfaces abstract and document when to switch between in-memory, SQLite, Neo4j, or Memgraph. +- Invest in evaluation tooling: competitors differentiate through measurable answer quality; MeshMind's planned analytics loops should ship early. +- Double down on automation: reproducible setup scripts and docs help MeshMind compete with managed services' ease of adoption. +- Maintain provider-agnostic LLM orchestration so organisations can negotiate cost/latency trade-offs without code changes. diff --git a/run/install_setup.sh b/run/install_setup.sh new file mode 100755 index 0000000..6ad4ff9 --- /dev/null +++ b/run/install_setup.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Provision a fresh MeshMind development environment with full optional coverage. +# This script assumes outbound internet access and root privileges. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" + +if [[ "${MESH_SKIP_SYSTEM_PACKAGES:-0}" == "1" ]]; then + echo "[${SCRIPT_NAME}] Skipping system package installation (MESH_SKIP_SYSTEM_PACKAGES=1)" +else + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + build-essential \ + cmake \ + curl \ + git \ + pkg-config \ + python3 \ + python3-dev \ + python3-venv \ + python3-pip \ + libssl-dev \ + libffi-dev \ + libkrb5-dev \ + libsasl2-dev \ + libpq-dev \ + libopenblas-dev \ + liblapack-dev +fi + +if [[ "${MESH_SKIP_PYTHON_SYNC:-0}" != "1" ]]; then + if ! command -v uv >/dev/null 2>&1; then + curl -LsSf https://astral.sh/uv/install.sh | sh -s -- --install-dir /usr/local/bin --force + fi +fi + +cd "${REPO_ROOT}" + +python - <<'PY' +import sys +from pathlib import Path + +try: + import tomllib # type: ignore +except ModuleNotFoundError: # pragma: no cover - Python <3.11 fallback + import tomli as tomllib # type: ignore + +data = tomllib.loads(Path("pyproject.toml").read_text()) +extras = data.get("project", {}).get("optional-dependencies", {}) +required = {"neo4j", "pymgclient", "fastapi"} +available: set[str] = set() +for group in ("dev", "docs", "testing"): + for dep in extras.get(group, []): + normalized = dep.split(";", 1)[0].split("[", 1)[0] + normalized = normalized.split("==", 1)[0].split(">=", 1)[0].strip() + if normalized: + available.add(normalized) +missing = required - available +if missing: + sys.stderr.write(f"Missing optional packages in extras: {', '.join(sorted(missing))}\n") + sys.exit(1) +print("Validated optional packages: " + ", ".join(sorted(required))) +PY + +if [[ "${MESH_SKIP_PYTHON_SYNC:-0}" == "1" ]]; then + echo "[${SCRIPT_NAME}] Skipping Python dependency sync (MESH_SKIP_PYTHON_SYNC=1)" +else + if [ -f "${REPO_ROOT}/uv.lock" ]; then + uv pip sync --system "${REPO_ROOT}/uv.lock" + else + uv pip install --system ".[dev,docs,testing]" + fi +fi + +# Ensure scripts are executable for convenience. +chmod +x run/*.sh || true + diff --git a/run/maintenance_setup.sh b/run/maintenance_setup.sh new file mode 100755 index 0000000..b20fb35 --- /dev/null +++ b/run/maintenance_setup.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Refresh an existing MeshMind environment restored from cache. +# Installs missing system packages, syncs Python dependencies, and +# prepares the workspace for end-to-end testing. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" + +if [[ "${MESH_SKIP_SYSTEM_PACKAGES:-0}" == "1" ]]; then + echo "[${SCRIPT_NAME}] Skipping system package installation (MESH_SKIP_SYSTEM_PACKAGES=1)" +else + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + build-essential \ + cmake \ + curl \ + git \ + pkg-config \ + python3 \ + python3-dev \ + python3-venv \ + python3-pip \ + libssl-dev \ + libffi-dev \ + libkrb5-dev \ + libsasl2-dev \ + libpq-dev \ + libopenblas-dev \ + liblapack-dev +fi + +if [[ "${MESH_SKIP_PYTHON_SYNC:-0}" != "1" ]]; then + if ! command -v uv >/dev/null 2>&1; then + curl -LsSf https://astral.sh/uv/install.sh | sh -s -- --install-dir /usr/local/bin --force + fi +fi + +cd "${REPO_ROOT}" + +python - <<'PY' +import sys +from pathlib import Path + +try: + import tomllib # type: ignore +except ModuleNotFoundError: # pragma: no cover - Python <3.11 fallback + import tomli as tomllib # type: ignore + +data = tomllib.loads(Path("pyproject.toml").read_text()) +extras = data.get("project", {}).get("optional-dependencies", {}) +required = {"neo4j", "pymgclient", "fastapi"} +available: set[str] = set() +for group in ("dev", "docs", "testing"): + for dep in extras.get(group, []): + normalized = dep.split(";", 1)[0].split("[", 1)[0] + normalized = normalized.split("==", 1)[0].split(">=", 1)[0].strip() + if normalized: + available.add(normalized) +missing = required - available +if missing: + sys.stderr.write(f"Missing optional packages in extras: {', '.join(sorted(missing))}\n") + sys.exit(1) +print("Validated optional packages: " + ", ".join(sorted(required))) +PY + +if [[ "${MESH_SKIP_PYTHON_SYNC:-0}" == "1" ]]; then + echo "[${SCRIPT_NAME}] Skipping Python dependency sync (MESH_SKIP_PYTHON_SYNC=1)" +else + if [ -f "${REPO_ROOT}/uv.lock" ]; then + uv pip sync --system "${REPO_ROOT}/uv.lock" + else + uv pip install --system ".[dev,docs,testing]" + fi +fi + +# Update git submodules (if any) to keep tooling consistent. +if [ -f .gitmodules ]; then + git submodule update --init --recursive +fi + diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/benchmark_pagination.py b/scripts/benchmark_pagination.py new file mode 100644 index 0000000..301da3a --- /dev/null +++ b/scripts/benchmark_pagination.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Measure pagination performance for MeshMind drivers using synthetic data.""" +from __future__ import annotations + +import argparse +import json +import sys +import time +from pathlib import Path +from statistics import mean, stdev + +sys.path.append(str(Path(__file__).resolve().parents[1])) + +from meshmind.api.memory_manager import MemoryManager +from meshmind.core.types import Memory +from meshmind.db.factory import create_graph_driver + + +def _seed(manager: MemoryManager, namespace: str, entity_label: str, count: int) -> None: + existing = manager.list_memories(namespace=namespace, entity_labels=[entity_label]) + if existing: + return + for idx in range(count): + mem = Memory( + namespace=namespace, + name=f"Pagination {idx}", + entity_label=entity_label, + metadata={"content": f"Synthetic payload {idx}"}, + ) + manager.add_memory(mem) + + +def run_benchmark(args: argparse.Namespace) -> dict[str, float]: + driver = create_graph_driver(backend=args.backend) + manager = MemoryManager(driver) + try: + _seed(manager, args.namespace, args.entity_label, args.count) + durations = [] + total_fetched = 0 + for _ in range(args.iterations): + start = time.perf_counter() + offset = 0 + fetched = 0 + while True: + batch = manager.list_memories( + namespace=args.namespace, + entity_labels=[args.entity_label], + offset=offset, + limit=args.page_size, + ) + fetched += len(batch) + if not batch: + break + offset += args.page_size + durations.append(time.perf_counter() - start) + total_fetched = fetched + return { + "iterations": float(args.iterations), + "duration_mean": round(mean(durations), 6), + "duration_stddev": round(stdev(durations) if len(durations) > 1 else 0.0, 6), + "page_size": float(args.page_size), + "count": float(args.count), + "fetched": float(total_fetched), + } + finally: + closer = getattr(driver, "close", None) + if callable(closer): + closer() + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--backend", default="memory", help="Driver backend to benchmark") + parser.add_argument("--count", type=int, default=2000, help="Synthetic memories to seed") + parser.add_argument("--page-size", type=int, default=200, help="Page size for list_memories") + parser.add_argument("--iterations", type=int, default=3, help="Number of benchmark repetitions") + parser.add_argument("--namespace", default="pagination", help="Namespace for synthetic data") + parser.add_argument("--entity-label", default="Memory", help="Entity label for seeded data") + parser.add_argument("--output", help="Optional JSON output path") + args = parser.parse_args() + + metrics = run_benchmark(args) + payload = json.dumps(metrics, indent=2, sort_keys=True) + if args.output: + Path(args.output).write_text(payload + "\n", encoding="utf-8") + print(payload) + + +if __name__ == "__main__": + main() diff --git a/scripts/check_docs_sync.py b/scripts/check_docs_sync.py new file mode 100755 index 0000000..a2495a9 --- /dev/null +++ b/scripts/check_docs_sync.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Ensure documentation updates accompany relevant code changes.""" + +from __future__ import annotations + +import argparse +import subprocess +import sys +from pathlib import Path +from typing import Dict, Iterable, List, Set + +PROJECT_ROOT = Path(__file__).resolve().parent.parent + +DOC_MAP: Dict[str, List[str]] = { + "meshmind/api": ["docs/api.md", "docs/operations.md"], + "meshmind/retrieval": ["docs/retrieval.md"], + "meshmind/db": ["docs/persistence.md"], + "meshmind/pipeline": ["docs/pipelines.md"], + "meshmind/core": ["docs/architecture.md", "docs/development.md"], + "meshmind/llm_client.py": ["docs/architecture.md", "docs/configuration.md"], + "meshmind/cli": ["docs/operations.md"], + "meshmind/tasks": ["docs/operations.md", "docs/telemetry.md"], + "meshmind/tests/docker": ["SETUP.md", "docs/operations.md"], + "docker-compose": ["SETUP.md", "docs/operations.md", "ENVIRONMENT_NEEDS.md"], + "Dockerfile": ["SETUP.md", "ENVIRONMENT_NEEDS.md"], +} + +DOC_PREFIXES = ("docs/",) +ALWAYS_DOC_FILES = { + "README.md", + "PROJECT.md", + "SOT.md", + "PLAN.md", + "RECOMMENDATIONS.md", + "RESUME_NOTES.md", + "SETUP.md", + "ENVIRONMENT_NEEDS.md", + "NEEDED_FOR_TESTING.md", +} + + +def _git_diff(base: str) -> List[str]: + cmd = ["git", "diff", "--name-only", f"{base}..HEAD"] + result = subprocess.run(cmd, capture_output=True, text=True, check=False) + if result.returncode != 0: + raise RuntimeError(result.stderr.strip() or "git diff failed") + return [line.strip() for line in result.stdout.splitlines() if line.strip()] + + +def _docs_touched(files: Iterable[str]) -> Set[str]: + docs: Set[str] = set() + for path in files: + if path.startswith(DOC_PREFIXES) or path in ALWAYS_DOC_FILES: + docs.add(path) + return docs + + +def check_docs(base: str) -> int: + changed = _git_diff(base) + if not changed: + return 0 + + docs_changed = _docs_touched(changed) + missing: Dict[str, Dict[str, Iterable[str]]] = {} + + for prefix, required_docs in DOC_MAP.items(): + touched = [path for path in changed if path.startswith(prefix)] + if not touched: + continue + if any(doc in docs_changed for doc in required_docs): + continue + missing[prefix] = {"files": touched, "docs": required_docs} + + if missing: + print("Documentation updates required for the following areas:", file=sys.stderr) + for prefix, info in missing.items(): + print(f"- {prefix}", file=sys.stderr) + print(f" touched: {', '.join(info['files'])}", file=sys.stderr) + print(f" expected docs: {', '.join(info['docs'])}", file=sys.stderr) + return 1 + + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--base", + default="origin/main", + help="Git reference to diff against (default: origin/main)", + ) + args = parser.parse_args() + + try: + return check_docs(args.base) + except RuntimeError as exc: # pragma: no cover - git errors depend on CI setup + print(str(exc), file=sys.stderr) + return 2 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/check_protos.py b/scripts/check_protos.py new file mode 100644 index 0000000..326ea55 --- /dev/null +++ b/scripts/check_protos.py @@ -0,0 +1,48 @@ +"""Verify generated protobuf bindings are up to date.""" +from __future__ import annotations + +from pathlib import Path +import subprocess +import sys +import tempfile + +PROTO_DIR = Path(__file__).resolve().parent.parent / "meshmind" / "protos" +PROTO_FILE = PROTO_DIR / "memory_service.proto" +GENERATED = ["memory_service_pb2.py", "memory_service_pb2_grpc.py"] + + +def main() -> None: + if not PROTO_FILE.exists(): + raise SystemExit(f"Missing proto definition: {PROTO_FILE}") + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + command = [ + sys.executable, + "-m", + "grpc_tools.protoc", + f"--proto_path={PROTO_DIR}", + f"--python_out={tmp_path}", + f"--grpc_python_out={tmp_path}", + str(PROTO_FILE), + ] + subprocess.check_call(command) + drift = [] + for filename in GENERATED: + target = PROTO_DIR / filename + candidate = tmp_path / filename + if not target.exists(): + drift.append(filename) + continue + if target.read_text(encoding="utf-8") != candidate.read_text(encoding="utf-8"): + drift.append(filename) + if drift: + joined = ", ".join(drift) + raise SystemExit( + "Outdated protobuf bindings detected: " + f"{joined}. Run `python scripts/generate_protos.py` and commit the results." + ) + print("Protobuf bindings are up to date.", file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/scripts/consolidation_benchmark.py b/scripts/consolidation_benchmark.py new file mode 100644 index 0000000..ab4f13b --- /dev/null +++ b/scripts/consolidation_benchmark.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Benchmark consolidation heuristics across synthetic workloads.""" +from __future__ import annotations + +import argparse +import json +import sys +import time +from pathlib import Path +from statistics import mean, stdev +from typing import List + +sys.path.append(str(Path(__file__).resolve().parents[1])) + +from meshmind.core.types import Memory +from meshmind.pipeline.consolidate import ( + ConsolidationOutcome, + ConsolidationSettings, + consolidate_memories, +) + + +def _generate_dataset(namespaces: int, groups_per_namespace: int, duplicates: int) -> List[Memory]: + memories: List[Memory] = [] + for ns_idx in range(namespaces): + namespace = f"benchmark-{ns_idx}" + for group_idx in range(groups_per_namespace): + label = f"Entity {ns_idx}-{group_idx}" + primary = Memory( + namespace=namespace, + name=label, + entity_label="Memory", + metadata={"content": f"{label} canonical"}, + importance=1.0, + ) + memories.append(primary) + for dup_idx in range(duplicates): + memories.append( + Memory( + namespace=namespace, + name=label, + entity_label="Memory", + metadata={ + "content": f"{label} duplicate {dup_idx}", + "summary": f"Summary {dup_idx}", + }, + importance=max(0.1, 0.9 - (dup_idx * 0.05)), + ) + ) + return memories + + +def _summarize(plan: List[ConsolidationOutcome]) -> dict[str, float]: + removed = sum(len(outcome.removed_ids) for outcome in plan) + summaries = sum( + 1 for outcome in plan if outcome.updated.metadata.get("consolidated_summary") + ) + return { + "groups": float(len(plan)), + "removed": float(removed), + "summaries": float(summaries), + } + + +def run_benchmark(args: argparse.Namespace) -> dict[str, float]: + dataset = _generate_dataset(args.namespaces, args.groups, args.duplicates) + settings = ConsolidationSettings( + max_group_size=max(args.duplicates + 1, args.max_group_size), + max_updates=args.max_updates, + max_updates_per_namespace=args.max_updates_per_namespace, + ) + durations = [] + for _ in range(args.iterations): + start = time.perf_counter() + plan = consolidate_memories(dataset, settings=settings) + durations.append(time.perf_counter() - start) + stats = _summarize(plan.outcomes) + stats.update( + { + "duration_mean": round(mean(durations), 6), + "duration_stddev": round(stdev(durations) if len(durations) > 1 else 0.0, 6), + "iterations": float(len(durations)), + } + ) + return stats + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--namespaces", type=int, default=3, help="Synthetic namespaces") + parser.add_argument("--groups", type=int, default=10, help="Groups per namespace") + parser.add_argument("--duplicates", type=int, default=7, help="Duplicates per group") + parser.add_argument("--iterations", type=int, default=5, help="Benchmark iterations") + parser.add_argument( + "--max-group-size", + type=int, + default=25, + help="Override ConsolidationSettings.max_group_size", + ) + parser.add_argument( + "--max-updates", + type=int, + default=400, + help="Override ConsolidationSettings.max_updates", + ) + parser.add_argument( + "--max-updates-per-namespace", + type=int, + default=150, + help="Override ConsolidationSettings.max_updates_per_namespace", + ) + parser.add_argument( + "--output", + help="Optional JSON file path to persist benchmark metrics", + ) + args = parser.parse_args() + + metrics = run_benchmark(args) + payload = json.dumps(metrics, indent=2, sort_keys=True) + if args.output: + Path(args.output).write_text(payload + "\n", encoding="utf-8") + print(payload) + + +if __name__ == "__main__": + main() diff --git a/scripts/evaluate_importance.py b/scripts/evaluate_importance.py new file mode 100644 index 0000000..a994b63 --- /dev/null +++ b/scripts/evaluate_importance.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""Evaluate MeshMind's importance heuristic across sample datasets.""" +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Iterable, List + +sys.path.append(str(Path(__file__).resolve().parents[1])) + +from meshmind.core.types import Memory +from meshmind.pipeline.preprocess import score_importance, summarize_importance + + +def _load_memories_from_path(path: Path) -> List[Memory]: + text = path.read_text(encoding="utf-8") + try: + payload = json.loads(text) + if isinstance(payload, list): + items = payload + else: + items = payload.get("memories", []) if isinstance(payload, dict) else [] + except json.JSONDecodeError: + # Try JSON lines fallback + items = [json.loads(line) for line in text.splitlines() if line.strip()] + memories = [] + for item in items: + if not isinstance(item, dict): + continue + memories.append(Memory(**item)) + return memories + + +def _generate_synthetic(count: int, namespace: str) -> List[Memory]: + memories: List[Memory] = [] + for idx in range(count): + memories.append( + Memory( + namespace=namespace, + name=f"Synthetic memory {idx}", + entity_label="Memory", + metadata={ + "content": f"Synthetic content block {idx}", + "summary": f"Auto summary {idx}", + }, + ) + ) + return memories + + +def _memories_from_args(args: argparse.Namespace) -> List[Memory]: + if args.input: + return _load_memories_from_path(Path(args.input)) + return _generate_synthetic(args.synthetic_count, args.namespace) + + +def evaluate(memories: Iterable[Memory]) -> dict[str, float]: + scored = score_importance(list(memories)) + return summarize_importance(scored) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--input", + help="Path to JSON/JSONL payload containing memories (falls back to synthetic data)", + ) + parser.add_argument( + "--synthetic-count", + type=int, + default=100, + help="Number of synthetic memories to generate when --input is omitted", + ) + parser.add_argument( + "--namespace", + default="analysis", + help="Namespace assigned to synthetic memories", + ) + parser.add_argument( + "--output", + help="Optional file path to store the evaluation summary as JSON", + ) + args = parser.parse_args() + + memories = _memories_from_args(args) + summary = evaluate(memories) + payload = {"count": summary["count"], "mean": summary["mean"], "stddev": summary["stddev"], "recent_bonus": summary["recent_bonus"]} + text = json.dumps(payload, indent=2, sort_keys=True) + + if args.output: + Path(args.output).write_text(text + "\n", encoding="utf-8") + print(text) + + +if __name__ == "__main__": + main() diff --git a/scripts/generate_protos.py b/scripts/generate_protos.py new file mode 100644 index 0000000..e402da0 --- /dev/null +++ b/scripts/generate_protos.py @@ -0,0 +1,29 @@ +"""Regenerate MeshMind protobuf bindings in-place.""" +from __future__ import annotations + +from pathlib import Path +import subprocess +import sys + +PROTO_DIR = Path(__file__).resolve().parent.parent / "meshmind" / "protos" +PROTO_FILE = PROTO_DIR / "memory_service.proto" + + +def main() -> None: + if not PROTO_FILE.exists(): + raise SystemExit(f"Missing proto definition: {PROTO_FILE}") + command = [ + sys.executable, + "-m", + "grpc_tools.protoc", + f"--proto_path={PROTO_DIR}", + f"--python_out={PROTO_DIR}", + f"--grpc_python_out={PROTO_DIR}", + str(PROTO_FILE), + ] + subprocess.check_call(command) + print("Regenerated protobuf bindings", file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock index c20d1b4..4ca8ec5 100644 --- a/uv.lock +++ b/uv.lock @@ -1,1267 +1,2575 @@ -version = 1 -requires-python = ">=3.13" +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml --extra dev --extra docs --extra testing --format pylock.toml -o pylock.toml +lock-version = "1.0" +created-by = "uv" +requires-python = ">=3.13.3" -[[package]] +[[packages]] name = "amqp" version = "5.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "vine" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", upload-time = 2024-11-12T19:55:44Z, size = 129013, hashes = { sha256 = "cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", upload-time = 2024-11-12T19:55:41Z, size = 50944, hashes = { sha256 = "43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2" } }] -[[package]] +[[packages]] 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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", upload-time = 2024-05-20T21:33:25Z, size = 16081, hashes = { sha256 = "aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", upload-time = 2024-05-20T21:33:24Z, size = 13643, hashes = { sha256 = "1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53" } }] -[[package]] +[[packages]] name = "anyio" -version = "4.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "sniffio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, -] - -[[package]] +version = "4.11.0" +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", upload-time = 2025-09-23T09:19:12Z, size = 219094, hashes = { sha256 = "82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", upload-time = 2025-09-23T09:19:10Z, size = 109097, hashes = { sha256 = "0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc" } }] + +[[packages]] +name = "babel" +version = "2.17.0" +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", upload-time = 2025-02-01T15:17:41Z, size = 9951852, hashes = { sha256 = "0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", upload-time = 2025-02-01T15:17:37Z, size = 10182537, hashes = { sha256 = "4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2" } }] + +[[packages]] +name = "backrefs" +version = "5.9" +sdist = { url = "https://files.pythonhosted.org/packages/eb/a7/312f673df6a79003279e1f55619abbe7daebbb87c17c976ddc0345c04c7b/backrefs-5.9.tar.gz", upload-time = 2025-06-22T19:34:13Z, size = 5765857, hashes = { sha256 = "808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/4d/798dc1f30468134906575156c089c492cf79b5a5fd373f07fe26c4d046bf/backrefs-5.9-py310-none-any.whl", upload-time = 2025-06-22T19:34:05Z, size = 380267, hashes = { sha256 = "db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f" } }, + { url = "https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl", upload-time = 2025-06-22T19:34:06Z, size = 392072, hashes = { sha256 = "6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf" } }, + { url = "https://files.pythonhosted.org/packages/9d/12/4f345407259dd60a0997107758ba3f221cf89a9b5a0f8ed5b961aef97253/backrefs-5.9-py312-none-any.whl", upload-time = 2025-06-22T19:34:08Z, size = 397947, hashes = { sha256 = "7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa" } }, + { url = "https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl", upload-time = 2025-06-22T19:34:09Z, size = 399843, hashes = { sha256 = "cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b" } }, + { url = "https://files.pythonhosted.org/packages/fc/24/b29af34b2c9c41645a9f4ff117bae860291780d73880f449e0b5d948c070/backrefs-5.9-py314-none-any.whl", upload-time = 2025-06-22T19:34:11Z, size = 411762, hashes = { sha256 = "df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9" } }, + { url = "https://files.pythonhosted.org/packages/41/ff/392bff89415399a979be4a65357a41d92729ae8580a66073d8ec8d810f98/backrefs-5.9-py39-none-any.whl", upload-time = 2025-06-22T19:34:12Z, size = 380265, hashes = { sha256 = "f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60" } }, +] + +[[packages]] name = "billiard" -version = "4.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/58/1546c970afcd2a2428b1bfafecf2371d8951cc34b46701bea73f4280989e/billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f", size = 155031 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/da/43b15f28fe5f9e027b41c539abc5469052e9d48fd75f8ff094ba2a0ae767/billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb", size = 86766 }, -] +version = "4.2.2" +sdist = { url = "https://files.pythonhosted.org/packages/b9/6a/1405343016bce8354b29d90aad6b0bf6485b5e60404516e4b9a3a9646cf0/billiard-4.2.2.tar.gz", upload-time = 2025-09-20T14:44:40Z, size = 155592, hashes = { sha256 = "e815017a062b714958463e07ba15981d802dc53d41c5b69d28c5a7c238f8ecf3" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/a6/80/ef8dff49aae0e4430f81842f7403e14e0ca59db7bbaf7af41245b67c6b25/billiard-4.2.2-py3-none-any.whl", upload-time = 2025-09-20T14:44:39Z, size = 86896, hashes = { sha256 = "4bc05dcf0d1cc6addef470723aac2a6232f3c7ed7475b0b580473a9145829457" } }] -[[package]] +[[packages]] name = "celery" -version = "5.5.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "billiard" }, - { name = "click" }, - { name = "click-didyoumean" }, - { name = "click-plugins" }, - { name = "click-repl" }, - { name = "kombu" }, - { name = "python-dateutil" }, - { name = "vine" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bf/03/5d9c6c449248958f1a5870e633a29d7419ff3724c452a98ffd22688a1a6a/celery-5.5.2.tar.gz", hash = "sha256:4d6930f354f9d29295425d7a37261245c74a32807c45d764bedc286afd0e724e", size = 1666892 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/94/8e825ac1cf59d45d20c4345d4461e6b5263ae475f708d047c3dad0ac6401/celery-5.5.2-py3-none-any.whl", hash = "sha256:54425a067afdc88b57cd8d94ed4af2ffaf13ab8c7680041ac2c4ac44357bdf4c", size = 438626 }, -] - -[package.optional-dependencies] -redis = [ - { name = "redis" }, -] +version = "5.5.3" +sdist = { url = "https://files.pythonhosted.org/packages/bb/7d/6c289f407d219ba36d8b384b42489ebdd0c84ce9c413875a8aae0c85f35b/celery-5.5.3.tar.gz", upload-time = 2025-06-01T11:08:12Z, size = 1667144, hashes = { sha256 = "6c972ae7968c2b5281227f01c3a3f984037d21c5129d07bf3550cc2afc6b10a5" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/c9/af/0dcccc7fdcdf170f9a1585e5e96b6fb0ba1749ef6be8c89a6202284759bd/celery-5.5.3-py3-none-any.whl", upload-time = 2025-06-01T11:08:09Z, size = 438775, hashes = { sha256 = "0b5761a07057acee94694464ca482416b959568904c9dfa41ce8413a7d65d525" } }] -[[package]] +[[packages]] name = "certifi" -version = "2025.4.26" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618 }, -] +version = "2025.10.5" +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", upload-time = 2025-10-05T04:12:15Z, size = 164519, hashes = { sha256 = "47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", upload-time = 2025-10-05T04:12:14Z, size = 163286, hashes = { sha256 = "0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de" } }] -[[package]] +[[packages]] name = "cffi" -version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, -] - -[[package]] +version = "2.0.0" +marker = "python_full_version >= '3.9' and platform_python_implementation != 'PyPy'" +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", upload-time = 2025-09-08T23:24:04Z, size = 523588, hashes = { sha256 = "44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", upload-time = 2025-09-08T23:22:08Z, size = 184283, hashes = { sha256 = "0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44" } }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-09-08T23:22:10Z, size = 180504, hashes = { sha256 = "f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49" } }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", upload-time = 2025-09-08T23:22:12Z, size = 208811, hashes = { sha256 = "53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c" } }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-08T23:22:13Z, size = 216402, hashes = { sha256 = "3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb" } }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", upload-time = 2025-09-08T23:22:14Z, size = 203217, hashes = { sha256 = "5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0" } }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", upload-time = 2025-09-08T23:22:15Z, size = 203079, hashes = { sha256 = "9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4" } }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-08T23:22:17Z, size = 216475, hashes = { sha256 = "fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453" } }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-09-08T23:22:19Z, size = 218829, hashes = { sha256 = "cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495" } }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", upload-time = 2025-09-08T23:22:20Z, size = 211211, hashes = { sha256 = "e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5" } }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-09-08T23:22:22Z, size = 218036, hashes = { sha256 = "8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb" } }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", upload-time = 2025-09-08T23:22:23Z, size = 172184, hashes = { sha256 = "1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a" } }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", upload-time = 2025-09-08T23:22:24Z, size = 182790, hashes = { sha256 = "b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739" } }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", upload-time = 2025-09-08T23:22:26Z, size = 184344, hashes = { sha256 = "b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe" } }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-09-08T23:22:28Z, size = 180560, hashes = { sha256 = "2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c" } }, + { 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", upload-time = 2025-09-08T23:22:29Z, size = 209613, hashes = { sha256 = "baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92" } }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-08T23:22:31Z, size = 216476, hashes = { sha256 = "730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93" } }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", upload-time = 2025-09-08T23:22:32Z, size = 203374, hashes = { sha256 = "6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5" } }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", upload-time = 2025-09-08T23:22:34Z, size = 202597, hashes = { sha256 = "9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664" } }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-08T23:22:35Z, size = 215574, hashes = { sha256 = "8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26" } }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-09-08T23:22:36Z, size = 218971, hashes = { sha256 = "a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9" } }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", upload-time = 2025-09-08T23:22:38Z, size = 211972, hashes = { sha256 = "94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414" } }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-09-08T23:22:39Z, size = 217078, hashes = { sha256 = "5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743" } }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", upload-time = 2025-09-08T23:22:40Z, size = 172076, hashes = { sha256 = "c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5" } }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", upload-time = 2025-09-08T23:22:42Z, size = 182820, hashes = { sha256 = "66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5" } }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", upload-time = 2025-09-08T23:22:43Z, size = 177635, hashes = { sha256 = "c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d" } }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2025-09-08T23:22:44Z, size = 185271, hashes = { sha256 = "6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d" } }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-09-08T23:22:45Z, size = 181048, hashes = { sha256 = "8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c" } }, + { 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", upload-time = 2025-09-08T23:22:47Z, size = 212529, hashes = { sha256 = "21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe" } }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-08T23:22:48Z, size = 220097, hashes = { sha256 = "b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062" } }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", upload-time = 2025-09-08T23:22:50Z, size = 207983, hashes = { sha256 = "1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e" } }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", upload-time = 2025-09-08T23:22:51Z, size = 206519, hashes = { sha256 = "81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037" } }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-08T23:22:52Z, size = 219572, hashes = { sha256 = "3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba" } }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-09-08T23:22:54Z, size = 222963, hashes = { sha256 = "3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94" } }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-09-08T23:22:55Z, size = 221361, hashes = { sha256 = "2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187" } }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", upload-time = 2025-09-08T23:22:57Z, size = 172932, hashes = { sha256 = "da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18" } }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", upload-time = 2025-09-08T23:22:58Z, size = 183557, hashes = { sha256 = "da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5" } }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", upload-time = 2025-09-08T23:22:59Z, size = 177762, hashes = { sha256 = "4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6" } }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-09-08T23:23:00Z, size = 185230, hashes = { sha256 = "00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb" } }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-09-08T23:23:02Z, size = 181043, hashes = { sha256 = "45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca" } }, + { 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", upload-time = 2025-09-08T23:23:03Z, size = 212446, hashes = { sha256 = "07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b" } }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-08T23:23:04Z, size = 220101, hashes = { sha256 = "d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b" } }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", upload-time = 2025-09-08T23:23:06Z, size = 207948, hashes = { sha256 = "f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2" } }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", upload-time = 2025-09-08T23:23:07Z, size = 206422, hashes = { sha256 = "dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3" } }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-08T23:23:09Z, size = 219499, hashes = { sha256 = "c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26" } }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-09-08T23:23:10Z, size = 222928, hashes = { sha256 = "d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c" } }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-09-08T23:23:12Z, size = 221302, hashes = { sha256 = "6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b" } }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", upload-time = 2025-09-08T23:23:14Z, size = 172909, hashes = { sha256 = "74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27" } }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", upload-time = 2025-09-08T23:23:15Z, size = 183402, hashes = { sha256 = "19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75" } }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", upload-time = 2025-09-08T23:23:16Z, size = 177780, hashes = { sha256 = "256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91" } }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", upload-time = 2025-09-08T23:23:18Z, size = 185320, hashes = { sha256 = "fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5" } }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-09-08T23:23:19Z, size = 181487, hashes = { sha256 = "c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13" } }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-08T23:23:20Z, size = 220049, hashes = { sha256 = "24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b" } }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", upload-time = 2025-09-08T23:23:22Z, size = 207793, hashes = { sha256 = "12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c" } }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", upload-time = 2025-09-08T23:23:23Z, size = 206300, hashes = { sha256 = "d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef" } }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-08T23:23:24Z, size = 219244, hashes = { sha256 = "afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775" } }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-09-08T23:23:26Z, size = 222828, hashes = { sha256 = "737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205" } }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-09-08T23:23:27Z, size = 220926, hashes = { sha256 = "38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1" } }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", upload-time = 2025-09-08T23:23:44Z, size = 175328, hashes = { sha256 = "087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f" } }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", upload-time = 2025-09-08T23:23:45Z, size = 185650, hashes = { sha256 = "203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25" } }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", upload-time = 2025-09-08T23:23:47Z, size = 180687, hashes = { sha256 = "dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad" } }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", upload-time = 2025-09-08T23:23:29Z, size = 188773, hashes = { sha256 = "9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9" } }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-09-08T23:23:30Z, size = 185013, hashes = { sha256 = "7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d" } }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-08T23:23:31Z, size = 221593, hashes = { sha256 = "7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c" } }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", upload-time = 2025-09-08T23:23:33Z, size = 209354, hashes = { sha256 = "92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8" } }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", upload-time = 2025-09-08T23:23:34Z, size = 208480, hashes = { sha256 = "b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc" } }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-08T23:23:36Z, size = 221584, hashes = { sha256 = "28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592" } }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", upload-time = 2025-09-08T23:23:37Z, size = 224443, hashes = { sha256 = "7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512" } }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", upload-time = 2025-09-08T23:23:38Z, size = 223437, hashes = { sha256 = "6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4" } }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", upload-time = 2025-09-08T23:23:40Z, size = 180487, hashes = { sha256 = "1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e" } }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", upload-time = 2025-09-08T23:23:41Z, size = 191726, hashes = { sha256 = "d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6" } }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", upload-time = 2025-09-08T23:23:43Z, size = 184195, hashes = { sha256 = "0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9" } }, + { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", upload-time = 2025-09-08T23:23:48Z, size = 184288, hashes = { sha256 = "fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf" } }, + { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2025-09-08T23:23:49Z, size = 180509, hashes = { sha256 = "de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7" } }, + { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", upload-time = 2025-09-08T23:23:51Z, size = 208813, hashes = { sha256 = "4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c" } }, + { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-08T23:23:52Z, size = 216498, hashes = { sha256 = "3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165" } }, + { url = "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", upload-time = 2025-09-08T23:23:53Z, size = 203243, hashes = { sha256 = "e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534" } }, + { url = "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", upload-time = 2025-09-08T23:23:55Z, size = 203158, hashes = { sha256 = "cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f" } }, + { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-08T23:23:56Z, size = 216548, hashes = { sha256 = "61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63" } }, + { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", upload-time = 2025-09-08T23:23:57Z, size = 218897, hashes = { sha256 = "0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2" } }, + { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", upload-time = 2025-09-08T23:23:59Z, size = 211249, hashes = { sha256 = "1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65" } }, + { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", upload-time = 2025-09-08T23:24:00Z, size = 218041, hashes = { sha256 = "89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322" } }, + { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", upload-time = 2025-09-08T23:24:01Z, size = 172138, hashes = { sha256 = "2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a" } }, + { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", upload-time = 2025-09-08T23:24:02Z, size = 182794, hashes = { sha256 = "b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9" } }, +] + +[[packages]] name = "charset-normalizer" -version = "3.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622 }, - { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435 }, - { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653 }, - { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231 }, - { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243 }, - { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442 }, - { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147 }, - { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057 }, - { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454 }, - { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174 }, - { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166 }, - { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064 }, - { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641 }, - { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 }, -] - -[[package]] +version = "3.4.4" +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", upload-time = 2025-10-14T04:42:32Z, size = 129418, hashes = { sha256 = "94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", upload-time = 2025-10-14T04:40:11Z, size = 209709, hashes = { sha256 = "e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d" } }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-14T04:40:13Z, size = 148814, hashes = { sha256 = "4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8" } }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", upload-time = 2025-10-14T04:40:14Z, size = 144467, hashes = { sha256 = "027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad" } }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-10-14T04:40:16Z, size = 162280, hashes = { sha256 = "f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8" } }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-10-14T04:40:17Z, size = 159454, hashes = { sha256 = "798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d" } }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-14T04:40:19Z, size = 153609, hashes = { sha256 = "9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313" } }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-14T04:40:20Z, size = 151849, hashes = { sha256 = "9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e" } }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-10-14T04:40:21Z, size = 151586, hashes = { sha256 = "077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93" } }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", upload-time = 2025-10-14T04:40:23Z, size = 145290, hashes = { sha256 = "244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0" } }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", upload-time = 2025-10-14T04:40:24Z, size = 163663, hashes = { sha256 = "64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84" } }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", upload-time = 2025-10-14T04:40:25Z, size = 151964, hashes = { sha256 = "faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e" } }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", upload-time = 2025-10-14T04:40:26Z, size = 161064, hashes = { sha256 = "6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db" } }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-10-14T04:40:28Z, size = 155015, hashes = { sha256 = "cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6" } }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", upload-time = 2025-10-14T04:40:29Z, size = 99792, hashes = { sha256 = "f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f" } }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", upload-time = 2025-10-14T04:40:30Z, size = 107198, hashes = { sha256 = "a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d" } }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", upload-time = 2025-10-14T04:40:32Z, size = 100262, hashes = { sha256 = "cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69" } }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", upload-time = 2025-10-14T04:40:33Z, size = 206988, hashes = { sha256 = "6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8" } }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-14T04:40:34Z, size = 147324, hashes = { sha256 = "5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0" } }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", upload-time = 2025-10-14T04:40:36Z, size = 142742, hashes = { sha256 = "a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3" } }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-10-14T04:40:37Z, size = 160863, hashes = { sha256 = "8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc" } }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-10-14T04:40:38Z, size = 157837, hashes = { sha256 = "d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897" } }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-14T04:40:40Z, size = 151550, hashes = { sha256 = "840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381" } }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-14T04:40:41Z, size = 149162, hashes = { sha256 = "ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815" } }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-10-14T04:40:42Z, size = 150019, hashes = { sha256 = "d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0" } }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", upload-time = 2025-10-14T04:40:43Z, size = 143310, hashes = { sha256 = "277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161" } }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", upload-time = 2025-10-14T04:40:44Z, size = 162022, hashes = { sha256 = "31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4" } }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", upload-time = 2025-10-14T04:40:46Z, size = 149383, hashes = { sha256 = "0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89" } }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", upload-time = 2025-10-14T04:40:47Z, size = 159098, hashes = { sha256 = "9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569" } }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-10-14T04:40:48Z, size = 152991, hashes = { sha256 = "ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224" } }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", upload-time = 2025-10-14T04:40:49Z, size = 99456, hashes = { sha256 = "eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a" } }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", upload-time = 2025-10-14T04:40:50Z, size = 106978, hashes = { sha256 = "5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016" } }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", upload-time = 2025-10-14T04:40:52Z, size = 99969, hashes = { sha256 = "65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1" } }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", upload-time = 2025-10-14T04:40:53Z, size = 208425, hashes = { sha256 = "0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394" } }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-14T04:40:54Z, size = 148162, hashes = { sha256 = "b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25" } }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", upload-time = 2025-10-14T04:40:55Z, size = 144558, hashes = { sha256 = "74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef" } }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-10-14T04:40:57Z, size = 161497, hashes = { sha256 = "f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d" } }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-10-14T04:40:58Z, size = 159240, hashes = { sha256 = "2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8" } }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-14T04:40:59Z, size = 153471, hashes = { sha256 = "11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86" } }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-14T04:41:00Z, size = 150864, hashes = { sha256 = "ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a" } }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-10-14T04:41:01Z, size = 150647, hashes = { sha256 = "21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f" } }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", upload-time = 2025-10-14T04:41:03Z, size = 145110, hashes = { sha256 = "5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc" } }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", upload-time = 2025-10-14T04:41:04Z, size = 162839, hashes = { sha256 = "5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf" } }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", upload-time = 2025-10-14T04:41:05Z, size = 150667, hashes = { sha256 = "d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15" } }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", upload-time = 2025-10-14T04:41:06Z, size = 160535, hashes = { sha256 = "af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9" } }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-10-14T04:41:08Z, size = 154816, hashes = { sha256 = "780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0" } }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", upload-time = 2025-10-14T04:41:09Z, size = 99694, hashes = { sha256 = "5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26" } }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", upload-time = 2025-10-14T04:41:10Z, size = 107131, hashes = { sha256 = "a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525" } }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", upload-time = 2025-10-14T04:41:11Z, size = 100390, hashes = { sha256 = "376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3" } }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", upload-time = 2025-10-14T04:41:13Z, size = 208091, hashes = { sha256 = "e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794" } }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-14T04:41:14Z, size = 147936, hashes = { sha256 = "6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed" } }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", upload-time = 2025-10-14T04:41:15Z, size = 144180, hashes = { sha256 = "3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72" } }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-10-14T04:41:16Z, size = 161346, hashes = { sha256 = "81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328" } }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-10-14T04:41:17Z, size = 158874, hashes = { sha256 = "5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede" } }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-14T04:41:19Z, size = 153076, hashes = { sha256 = "a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894" } }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-14T04:41:20Z, size = 150601, hashes = { sha256 = "bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1" } }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-10-14T04:41:21Z, size = 150376, hashes = { sha256 = "f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490" } }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", upload-time = 2025-10-14T04:41:22Z, size = 144825, hashes = { sha256 = "554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44" } }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", upload-time = 2025-10-14T04:41:23Z, size = 162583, hashes = { sha256 = "74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133" } }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", upload-time = 2025-10-14T04:41:25Z, size = 150366, hashes = { sha256 = "c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3" } }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", upload-time = 2025-10-14T04:41:26Z, size = 160300, hashes = { sha256 = "362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e" } }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-10-14T04:41:28Z, size = 154465, hashes = { sha256 = "9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc" } }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", upload-time = 2025-10-14T04:41:29Z, size = 99404, hashes = { sha256 = "9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac" } }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", upload-time = 2025-10-14T04:41:31Z, size = 107092, hashes = { sha256 = "b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14" } }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", upload-time = 2025-10-14T04:41:32Z, size = 100408, hashes = { sha256 = "542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2" } }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", upload-time = 2025-10-14T04:41:33Z, size = 207746, hashes = { sha256 = "da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd" } }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-14T04:41:34Z, size = 147889, hashes = { sha256 = "8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb" } }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", upload-time = 2025-10-14T04:41:36Z, size = 143641, hashes = { sha256 = "74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e" } }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-10-14T04:41:37Z, size = 160779, hashes = { sha256 = "752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14" } }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-10-14T04:41:38Z, size = 159035, hashes = { sha256 = "d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191" } }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-14T04:41:39Z, size = 152542, hashes = { sha256 = "ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838" } }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-14T04:41:41Z, size = 149524, hashes = { sha256 = "cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6" } }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-10-14T04:41:42Z, size = 150395, hashes = { sha256 = "c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e" } }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", upload-time = 2025-10-14T04:41:43Z, size = 143680, hashes = { sha256 = "47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c" } }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", upload-time = 2025-10-14T04:41:44Z, size = 162045, hashes = { sha256 = "82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090" } }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", upload-time = 2025-10-14T04:41:46Z, size = 149687, hashes = { sha256 = "2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152" } }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", upload-time = 2025-10-14T04:41:47Z, size = 160014, hashes = { sha256 = "799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828" } }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-10-14T04:41:48Z, size = 154044, hashes = { sha256 = "99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec" } }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", upload-time = 2025-10-14T04:41:49Z, size = 99940, hashes = { sha256 = "f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9" } }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", upload-time = 2025-10-14T04:41:51Z, size = 107104, hashes = { sha256 = "8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c" } }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", upload-time = 2025-10-14T04:41:52Z, size = 100743, hashes = { sha256 = "de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2" } }, + { url = "https://files.pythonhosted.org/packages/0a/4e/3926a1c11f0433791985727965263f788af00db3482d89a7545ca5ecc921/charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", upload-time = 2025-10-14T04:41:53Z, size = 198599, hashes = { sha256 = "ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84" } }, + { url = "https://files.pythonhosted.org/packages/ec/7c/b92d1d1dcffc34592e71ea19c882b6709e43d20fa498042dea8b815638d7/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-14T04:41:54Z, size = 143090, hashes = { sha256 = "eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3" } }, + { url = "https://files.pythonhosted.org/packages/84/ce/61a28d3bb77281eb24107b937a497f3c43089326d27832a63dcedaab0478/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", upload-time = 2025-10-14T04:41:55Z, size = 139490, hashes = { sha256 = "c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac" } }, + { url = "https://files.pythonhosted.org/packages/c0/bd/c9e59a91b2061c6f8bb98a150670cb16d4cd7c4ba7d11ad0cdf789155f41/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-10-14T04:41:56Z, size = 155334, hashes = { sha256 = "2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af" } }, + { url = "https://files.pythonhosted.org/packages/bf/37/f17ae176a80f22ff823456af91ba3bc59df308154ff53aef0d39eb3d3419/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-10-14T04:41:58Z, size = 152823, hashes = { sha256 = "778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2" } }, + { url = "https://files.pythonhosted.org/packages/bf/fa/cf5bb2409a385f78750e78c8d2e24780964976acdaaed65dbd6083ae5b40/charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-14T04:41:59Z, size = 147618, hashes = { sha256 = "f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d" } }, + { url = "https://files.pythonhosted.org/packages/9b/63/579784a65bc7de2d4518d40bb8f1870900163e86f17f21fd1384318c459d/charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-14T04:42:00Z, size = 145516, hashes = { sha256 = "a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3" } }, + { url = "https://files.pythonhosted.org/packages/a3/a9/94ec6266cd394e8f93a4d69cca651d61bf6ac58d2a0422163b30c698f2c7/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", upload-time = 2025-10-14T04:42:01Z, size = 145266, hashes = { sha256 = "194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63" } }, + { url = "https://files.pythonhosted.org/packages/09/14/d6626eb97764b58c2779fa7928fa7d1a49adb8ce687c2dbba4db003c1939/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", upload-time = 2025-10-14T04:42:02Z, size = 139559, hashes = { sha256 = "6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7" } }, + { url = "https://files.pythonhosted.org/packages/09/01/ddbe6b01313ba191dbb0a43c7563bc770f2448c18127f9ea4b119c44dff0/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", upload-time = 2025-10-14T04:42:04Z, size = 156653, hashes = { sha256 = "cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4" } }, + { url = "https://files.pythonhosted.org/packages/95/c8/d05543378bea89296e9af4510b44c704626e191da447235c8fdedfc5b7b2/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", upload-time = 2025-10-14T04:42:05Z, size = 145644, hashes = { sha256 = "b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf" } }, + { url = "https://files.pythonhosted.org/packages/72/01/2866c4377998ef8a1f6802f6431e774a4c8ebe75b0a6e569ceec55c9cbfb/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", upload-time = 2025-10-14T04:42:06Z, size = 153964, hashes = { sha256 = "e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074" } }, + { url = "https://files.pythonhosted.org/packages/4a/66/66c72468a737b4cbd7851ba2c522fe35c600575fbeac944460b4fd4a06fe/charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", upload-time = 2025-10-14T04:42:07Z, size = 148777, hashes = { sha256 = "5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a" } }, + { url = "https://files.pythonhosted.org/packages/50/94/d0d56677fdddbffa8ca00ec411f67bb8c947f9876374ddc9d160d4f2c4b3/charset_normalizer-3.4.4-cp38-cp38-win32.whl", upload-time = 2025-10-14T04:42:08Z, size = 98687, hashes = { sha256 = "837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa" } }, + { url = "https://files.pythonhosted.org/packages/00/64/c3bc303d1b586480b1c8e6e1e2191a6d6dd40255244e5cf16763dcec52e6/charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", upload-time = 2025-10-14T04:42:09Z, size = 106115, hashes = { sha256 = "44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576" } }, + { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", upload-time = 2025-10-14T04:42:10Z, size = 209609, hashes = { sha256 = "a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9" } }, + { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-14T04:42:12Z, size = 149029, hashes = { sha256 = "1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d" } }, + { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", upload-time = 2025-10-14T04:42:13Z, size = 144580, hashes = { sha256 = "fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" } }, + { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-10-14T04:42:14Z, size = 162340, hashes = { sha256 = "0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc" } }, + { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-10-14T04:42:16Z, size = 159619, hashes = { sha256 = "cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e" } }, + { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-14T04:42:17Z, size = 153980, hashes = { sha256 = "4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1" } }, + { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-14T04:42:19Z, size = 152174, hashes = { sha256 = "fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3" } }, + { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", upload-time = 2025-10-14T04:42:20Z, size = 151666, hashes = { sha256 = "7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6" } }, + { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", upload-time = 2025-10-14T04:42:21Z, size = 145550, hashes = { sha256 = "5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88" } }, + { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", upload-time = 2025-10-14T04:42:22Z, size = 163721, hashes = { sha256 = "4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1" } }, + { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", upload-time = 2025-10-14T04:42:23Z, size = 152127, hashes = { sha256 = "7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf" } }, + { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", upload-time = 2025-10-14T04:42:24Z, size = 161175, hashes = { sha256 = "2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318" } }, + { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", upload-time = 2025-10-14T04:42:27Z, size = 155375, hashes = { sha256 = "cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c" } }, + { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", upload-time = 2025-10-14T04:42:28Z, size = 99692, hashes = { sha256 = "2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505" } }, + { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", upload-time = 2025-10-14T04:42:29Z, size = 107192, hashes = { sha256 = "f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966" } }, + { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", upload-time = 2025-10-14T04:42:30Z, size = 100220, hashes = { sha256 = "b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50" } }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", upload-time = 2025-10-14T04:42:31Z, size = 53402, hashes = { sha256 = "7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f" } }, +] + +[[packages]] name = "click" -version = "8.1.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, -] +version = "8.3.0" +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", upload-time = 2025-09-18T17:32:23Z, size = 276943, hashes = { sha256 = "e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", upload-time = 2025-09-18T17:32:22Z, size = 107295, hashes = { sha256 = "9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc" } }] -[[package]] +[[packages]] name = "click-didyoumean" version = "0.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", upload-time = 2024-03-24T08:22:07Z, size = 3089, hashes = { sha256 = "4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", upload-time = 2024-03-24T08:22:06Z, size = 3631, hashes = { sha256 = "5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c" } }] -[[package]] +[[packages]] name = "click-plugins" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5f/1d/45434f64ed749540af821fd7e42b8e4d23ac04b1eda7c26613288d6cd8a8/click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", size = 8164 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8", size = 7497 }, -] +version = "1.1.1.2" +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", upload-time = 2025-06-25T00:47:37Z, size = 8343, hashes = { sha256 = "d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", upload-time = 2025-06-25T00:47:36Z, size = 11051, hashes = { sha256 = "008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6" } }] -[[package]] +[[packages]] name = "click-repl" version = "0.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "prompt-toolkit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", upload-time = 2023-06-15T12:43:51Z, size = 10449, hashes = { sha256 = "17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", upload-time = 2023-06-15T12:43:48Z, size = 10289, hashes = { sha256 = "fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812" } }] -[[package]] +[[packages]] 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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - -[[package]] +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", upload-time = 2022-10-25T02:36:22Z, size = 27697, hashes = { sha256 = "08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", upload-time = 2022-10-25T02:36:20Z, size = 25335, hashes = { sha256 = "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" } }] + +[[packages]] +name = "coverage" +version = "7.11.0" +sdist = { url = "https://files.pythonhosted.org/packages/1c/38/ee22495420457259d2f3390309505ea98f98a5eed40901cf62196abad006/coverage-7.11.0.tar.gz", upload-time = 2025-10-15T15:15:08Z, size = 811905, hashes = { sha256 = "167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/95/c49df0aceb5507a80b9fe5172d3d39bf23f05be40c23c8d77d556df96cec/coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", upload-time = 2025-10-15T15:12:19Z, size = 215800, hashes = { sha256 = "eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31" } }, + { url = "https://files.pythonhosted.org/packages/dc/c6/7bb46ce01ed634fff1d7bb53a54049f539971862cc388b304ff3c51b4f66/coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:12:22Z, size = 216198, hashes = { sha256 = "d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075" } }, + { url = "https://files.pythonhosted.org/packages/94/b2/75d9d8fbf2900268aca5de29cd0a0fe671b0f69ef88be16767cc3c828b85/coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", upload-time = 2025-10-15T15:12:24Z, size = 242953, hashes = { sha256 = "0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab" } }, + { url = "https://files.pythonhosted.org/packages/65/ac/acaa984c18f440170525a8743eb4b6c960ace2dbad80dc22056a437fc3c6/coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-15T15:12:25Z, size = 244766, hashes = { sha256 = "e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0" } }, + { url = "https://files.pythonhosted.org/packages/d8/0d/938d0bff76dfa4a6b228c3fc4b3e1c0e2ad4aa6200c141fcda2bd1170227/coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:12:27Z, size = 246625, hashes = { sha256 = "596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785" } }, + { url = "https://files.pythonhosted.org/packages/38/54/8f5f5e84bfa268df98f46b2cb396b1009734cfb1e5d6adb663d284893b32/coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-15T15:12:28Z, size = 243568, hashes = { sha256 = "ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591" } }, + { url = "https://files.pythonhosted.org/packages/68/30/8ba337c2877fe3f2e1af0ed7ff4be0c0c4aca44d6f4007040f3ca2255e99/coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T15:12:30Z, size = 244665, hashes = { sha256 = "9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088" } }, + { url = "https://files.pythonhosted.org/packages/cc/fb/c6f1d6d9a665536b7dde2333346f0cc41dc6a60bd1ffc10cd5c33e7eb000/coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", upload-time = 2025-10-15T15:12:32Z, size = 242681, hashes = { sha256 = "e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f" } }, + { url = "https://files.pythonhosted.org/packages/be/38/1b532319af5f991fa153c20373291dc65c2bf532af7dbcffdeef745c8f79/coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", upload-time = 2025-10-15T15:12:34Z, size = 242912, hashes = { sha256 = "7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866" } }, + { url = "https://files.pythonhosted.org/packages/67/3d/f39331c60ef6050d2a861dc1b514fa78f85f792820b68e8c04196ad733d6/coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T15:12:35Z, size = 243559, hashes = { sha256 = "3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841" } }, + { url = "https://files.pythonhosted.org/packages/4b/55/cb7c9df9d0495036ce582a8a2958d50c23cd73f84a23284bc23bd4711a6f/coverage-7.11.0-cp310-cp310-win32.whl", upload-time = 2025-10-15T15:12:37Z, size = 218266, hashes = { sha256 = "765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf" } }, + { url = "https://files.pythonhosted.org/packages/68/a8/b79cb275fa7bd0208767f89d57a1b5f6ba830813875738599741b97c2e04/coverage-7.11.0-cp310-cp310-win_amd64.whl", upload-time = 2025-10-15T15:12:39Z, size = 219169, hashes = { sha256 = "24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969" } }, + { url = "https://files.pythonhosted.org/packages/49/3a/ee1074c15c408ddddddb1db7dd904f6b81bc524e01f5a1c5920e13dbde23/coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", upload-time = 2025-10-15T15:12:40Z, size = 215912, hashes = { sha256 = "3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847" } }, + { url = "https://files.pythonhosted.org/packages/70/c4/9f44bebe5cb15f31608597b037d78799cc5f450044465bcd1ae8cb222fe1/coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:12:42Z, size = 216310, hashes = { sha256 = "b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc" } }, + { url = "https://files.pythonhosted.org/packages/42/01/5e06077cfef92d8af926bdd86b84fb28bf9bc6ad27343d68be9b501d89f2/coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", upload-time = 2025-10-15T15:12:44Z, size = 246706, hashes = { sha256 = "ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0" } }, + { url = "https://files.pythonhosted.org/packages/40/b8/7a3f1f33b35cc4a6c37e759137533119560d06c0cc14753d1a803be0cd4a/coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-15T15:12:45Z, size = 248634, hashes = { sha256 = "aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7" } }, + { url = "https://files.pythonhosted.org/packages/7a/41/7f987eb33de386bc4c665ab0bf98d15fcf203369d6aacae74f5dd8ec489a/coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:12:47Z, size = 250741, hashes = { sha256 = "a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623" } }, + { url = "https://files.pythonhosted.org/packages/23/c1/a4e0ca6a4e83069fb8216b49b30a7352061ca0cb38654bd2dc96b7b3b7da/coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-15T15:12:48Z, size = 246837, hashes = { sha256 = "e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287" } }, + { url = "https://files.pythonhosted.org/packages/5d/03/ced062a17f7c38b4728ff76c3acb40d8465634b20b4833cdb3cc3a74e115/coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T15:12:50Z, size = 248429, hashes = { sha256 = "bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552" } }, + { url = "https://files.pythonhosted.org/packages/97/af/a7c6f194bb8c5a2705ae019036b8fe7f49ea818d638eedb15fdb7bed227c/coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", upload-time = 2025-10-15T15:12:52Z, size = 246490, hashes = { sha256 = "214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de" } }, + { url = "https://files.pythonhosted.org/packages/ab/c3/aab4df02b04a8fde79068c3c41ad7a622b0ef2b12e1ed154da986a727c3f/coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", upload-time = 2025-10-15T15:12:54Z, size = 246208, hashes = { sha256 = "258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601" } }, + { url = "https://files.pythonhosted.org/packages/30/d8/e282ec19cd658238d60ed404f99ef2e45eed52e81b866ab1518c0d4163cf/coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T15:12:56Z, size = 247126, hashes = { sha256 = "cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e" } }, + { url = "https://files.pythonhosted.org/packages/d1/17/a635fa07fac23adb1a5451ec756216768c2767efaed2e4331710342a3399/coverage-7.11.0-cp311-cp311-win32.whl", upload-time = 2025-10-15T15:12:58Z, size = 218314, hashes = { sha256 = "fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c" } }, + { url = "https://files.pythonhosted.org/packages/2a/29/2ac1dfcdd4ab9a70026edc8d715ece9b4be9a1653075c658ee6f271f394d/coverage-7.11.0-cp311-cp311-win_amd64.whl", upload-time = 2025-10-15T15:12:59Z, size = 219203, hashes = { sha256 = "865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9" } }, + { url = "https://files.pythonhosted.org/packages/03/21/5ce8b3a0133179115af4c041abf2ee652395837cb896614beb8ce8ddcfd9/coverage-7.11.0-cp311-cp311-win_arm64.whl", upload-time = 2025-10-15T15:13:01Z, size = 217879, hashes = { sha256 = "5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745" } }, + { url = "https://files.pythonhosted.org/packages/c4/db/86f6906a7c7edc1a52b2c6682d6dd9be775d73c0dfe2b84f8923dfea5784/coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T15:13:02Z, size = 216098, hashes = { sha256 = "9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1" } }, + { url = "https://files.pythonhosted.org/packages/21/54/e7b26157048c7ba555596aad8569ff903d6cd67867d41b75287323678ede/coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:13:04Z, size = 216331, hashes = { sha256 = "a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007" } }, + { url = "https://files.pythonhosted.org/packages/b9/19/1ce6bf444f858b83a733171306134a0544eaddf1ca8851ede6540a55b2ad/coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", upload-time = 2025-10-15T15:13:05Z, size = 247825, hashes = { sha256 = "c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46" } }, + { url = "https://files.pythonhosted.org/packages/71/0b/d3bcbbc259fcced5fb67c5d78f6e7ee965f49760c14afd931e9e663a83b2/coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-15T15:13:07Z, size = 250573, hashes = { sha256 = "16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893" } }, + { url = "https://files.pythonhosted.org/packages/58/8d/b0ff3641a320abb047258d36ed1c21d16be33beed4152628331a1baf3365/coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:13:09Z, size = 251706, hashes = { sha256 = "80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115" } }, + { url = "https://files.pythonhosted.org/packages/59/c8/5a586fe8c7b0458053d9c687f5cff515a74b66c85931f7fe17a1c958b4ac/coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-15T15:13:10Z, size = 248221, hashes = { sha256 = "4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415" } }, + { url = "https://files.pythonhosted.org/packages/d0/ff/3a25e3132804ba44cfa9a778cdf2b73dbbe63ef4b0945e39602fc896ba52/coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T15:13:12Z, size = 249624, hashes = { sha256 = "a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186" } }, + { url = "https://files.pythonhosted.org/packages/c5/12/ff10c8ce3895e1b17a73485ea79ebc1896a9e466a9d0f4aef63e0d17b718/coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", upload-time = 2025-10-15T15:13:14Z, size = 247744, hashes = { sha256 = "fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d" } }, + { url = "https://files.pythonhosted.org/packages/16/02/d500b91f5471b2975947e0629b8980e5e90786fe316b6d7299852c1d793d/coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", upload-time = 2025-10-15T15:13:16Z, size = 247325, hashes = { sha256 = "dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d" } }, + { url = "https://files.pythonhosted.org/packages/77/11/dee0284fbbd9cd64cfce806b827452c6df3f100d9e66188e82dfe771d4af/coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T15:13:17Z, size = 249180, hashes = { sha256 = "81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2" } }, + { url = "https://files.pythonhosted.org/packages/59/1b/cdf1def928f0a150a057cab03286774e73e29c2395f0d30ce3d9e9f8e697/coverage-7.11.0-cp312-cp312-win32.whl", upload-time = 2025-10-15T15:13:19Z, size = 218479, hashes = { sha256 = "037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5" } }, + { url = "https://files.pythonhosted.org/packages/ff/55/e5884d55e031da9c15b94b90a23beccc9d6beee65e9835cd6da0a79e4f3a/coverage-7.11.0-cp312-cp312-win_amd64.whl", upload-time = 2025-10-15T15:13:21Z, size = 219290, hashes = { sha256 = "d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0" } }, + { url = "https://files.pythonhosted.org/packages/23/a8/faa930cfc71c1d16bc78f9a19bb73700464f9c331d9e547bfbc1dbd3a108/coverage-7.11.0-cp312-cp312-win_arm64.whl", upload-time = 2025-10-15T15:13:23Z, size = 217924, hashes = { sha256 = "d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad" } }, + { url = "https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T15:13:25Z, size = 216129, hashes = { sha256 = "cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1" } }, + { url = "https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:13:26Z, size = 216380, hashes = { sha256 = "f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be" } }, + { url = "https://files.pythonhosted.org/packages/b2/f5/3da9cc9596708273385189289c0e4d8197d37a386bdf17619013554b3447/coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", upload-time = 2025-10-15T15:13:28Z, size = 247375, hashes = { sha256 = "7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d" } }, + { url = "https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-15T15:13:30Z, size = 249978, hashes = { sha256 = "10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82" } }, + { url = "https://files.pythonhosted.org/packages/e7/8c/042dede2e23525e863bf1ccd2b92689692a148d8b5fd37c37899ba882645/coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:13:32Z, size = 251253, hashes = { sha256 = "4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52" } }, + { url = "https://files.pythonhosted.org/packages/7b/a9/3c58df67bfa809a7bddd786356d9c5283e45d693edb5f3f55d0986dd905a/coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-15T15:13:34Z, size = 247591, hashes = { sha256 = "7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b" } }, + { url = "https://files.pythonhosted.org/packages/26/5b/c7f32efd862ee0477a18c41e4761305de6ddd2d49cdeda0c1116227570fd/coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T15:13:38Z, size = 249411, hashes = { sha256 = "59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4" } }, + { url = "https://files.pythonhosted.org/packages/76/b5/78cb4f1e86c1611431c990423ec0768122905b03837e1b4c6a6f388a858b/coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", upload-time = 2025-10-15T15:13:40Z, size = 247303, hashes = { sha256 = "df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd" } }, + { url = "https://files.pythonhosted.org/packages/87/c9/23c753a8641a330f45f221286e707c427e46d0ffd1719b080cedc984ec40/coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", upload-time = 2025-10-15T15:13:42Z, size = 247157, hashes = { sha256 = "8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc" } }, + { url = "https://files.pythonhosted.org/packages/c5/42/6e0cc71dc8a464486e944a4fa0d85bdec031cc2969e98ed41532a98336b9/coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T15:13:43Z, size = 248921, hashes = { sha256 = "5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48" } }, + { url = "https://files.pythonhosted.org/packages/e8/1c/743c2ef665e6858cccb0f84377dfe3a4c25add51e8c7ef19249be92465b6/coverage-7.11.0-cp313-cp313-win32.whl", upload-time = 2025-10-15T15:13:45Z, size = 218526, hashes = { sha256 = "695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040" } }, + { url = "https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl", upload-time = 2025-10-15T15:13:47Z, size = 219317, hashes = { sha256 = "2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05" } }, + { url = "https://files.pythonhosted.org/packages/97/54/47db81dcbe571a48a298f206183ba8a7ba79200a37cd0d9f4788fcd2af4a/coverage-7.11.0-cp313-cp313-win_arm64.whl", upload-time = 2025-10-15T15:13:49Z, size = 217948, hashes = { sha256 = "0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a" } }, + { url = "https://files.pythonhosted.org/packages/e5/8b/cb68425420154e7e2a82fd779a8cc01549b6fa83c2ad3679cd6c088ebd07/coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T15:13:51Z, size = 216837, hashes = { sha256 = "587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b" } }, + { url = "https://files.pythonhosted.org/packages/33/55/9d61b5765a025685e14659c8d07037247de6383c0385757544ffe4606475/coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:13:52Z, size = 217061, hashes = { sha256 = "b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37" } }, + { url = "https://files.pythonhosted.org/packages/52/85/292459c9186d70dcec6538f06ea251bc968046922497377bf4a1dc9a71de/coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", upload-time = 2025-10-15T15:13:54Z, size = 258398, hashes = { sha256 = "269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de" } }, + { url = "https://files.pythonhosted.org/packages/1f/e2/46edd73fb8bf51446c41148d81944c54ed224854812b6ca549be25113ee0/coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-15T15:13:56Z, size = 260574, hashes = { sha256 = "dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f" } }, + { url = "https://files.pythonhosted.org/packages/07/5e/1df469a19007ff82e2ca8fe509822820a31e251f80ee7344c34f6cd2ec43/coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:13:58Z, size = 262797, hashes = { sha256 = "9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c" } }, + { url = "https://files.pythonhosted.org/packages/f9/50/de216b31a1434b94d9b34a964c09943c6be45069ec704bfc379d8d89a649/coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-15T15:14:00Z, size = 257361, hashes = { sha256 = "9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa" } }, + { url = "https://files.pythonhosted.org/packages/82/1e/3f9f8344a48111e152e0fd495b6fff13cc743e771a6050abf1627a7ba918/coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T15:14:02Z, size = 260349, hashes = { sha256 = "eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740" } }, + { url = "https://files.pythonhosted.org/packages/65/9b/3f52741f9e7d82124272f3070bbe316006a7de1bad1093f88d59bfc6c548/coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", upload-time = 2025-10-15T15:14:03Z, size = 258114, hashes = { sha256 = "d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef" } }, + { url = "https://files.pythonhosted.org/packages/0b/8b/918f0e15f0365d50d3986bbd3338ca01178717ac5678301f3f547b6619e6/coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", upload-time = 2025-10-15T15:14:06Z, size = 256723, hashes = { sha256 = "6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0" } }, + { url = "https://files.pythonhosted.org/packages/44/9e/7776829f82d3cf630878a7965a7d70cc6ca94f22c7d20ec4944f7148cb46/coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T15:14:08Z, size = 259238, hashes = { sha256 = "dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca" } }, + { url = "https://files.pythonhosted.org/packages/9a/b8/49cf253e1e7a3bedb85199b201862dd7ca4859f75b6cf25ffa7298aa0760/coverage-7.11.0-cp313-cp313t-win32.whl", upload-time = 2025-10-15T15:14:09Z, size = 219180, hashes = { sha256 = "cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2" } }, + { url = "https://files.pythonhosted.org/packages/ac/e1/1a541703826be7ae2125a0fb7f821af5729d56bb71e946e7b933cc7a89a4/coverage-7.11.0-cp313-cp313t-win_amd64.whl", upload-time = 2025-10-15T15:14:11Z, size = 220241, hashes = { sha256 = "a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268" } }, + { url = "https://files.pythonhosted.org/packages/d5/d1/5ee0e0a08621140fd418ec4020f595b4d52d7eb429ae6a0c6542b4ba6f14/coverage-7.11.0-cp313-cp313t-win_arm64.whl", upload-time = 2025-10-15T15:14:13Z, size = 218510, hashes = { sha256 = "f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836" } }, + { url = "https://files.pythonhosted.org/packages/f4/06/e923830c1985ce808e40a3fa3eb46c13350b3224b7da59757d37b6ce12b8/coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T15:14:15Z, size = 216110, hashes = { sha256 = "c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497" } }, + { url = "https://files.pythonhosted.org/packages/42/82/cdeed03bfead45203fb651ed756dfb5266028f5f939e7f06efac4041dad5/coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:14:16Z, size = 216395, hashes = { sha256 = "a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e" } }, + { url = "https://files.pythonhosted.org/packages/fc/ba/e1c80caffc3199aa699813f73ff097bc2df7b31642bdbc7493600a8f1de5/coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", upload-time = 2025-10-15T15:14:18Z, size = 247433, hashes = { sha256 = "73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1" } }, + { url = "https://files.pythonhosted.org/packages/80/c0/5b259b029694ce0a5bbc1548834c7ba3db41d3efd3474489d7efce4ceb18/coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-15T15:14:20Z, size = 249970, hashes = { sha256 = "c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca" } }, + { url = "https://files.pythonhosted.org/packages/8c/86/171b2b5e1aac7e2fd9b43f7158b987dbeb95f06d1fbecad54ad8163ae3e8/coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:14:22Z, size = 251324, hashes = { sha256 = "e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd" } }, + { url = "https://files.pythonhosted.org/packages/1a/7e/7e10414d343385b92024af3932a27a1caf75c6e27ee88ba211221ff1a145/coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-15T15:14:24Z, size = 247445, hashes = { sha256 = "8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43" } }, + { url = "https://files.pythonhosted.org/packages/c4/3b/e4f966b21f5be8c4bf86ad75ae94efa0de4c99c7bbb8114476323102e345/coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T15:14:26Z, size = 249324, hashes = { sha256 = "a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777" } }, + { url = "https://files.pythonhosted.org/packages/00/a2/8479325576dfcd909244d0df215f077f47437ab852ab778cfa2f8bf4d954/coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", upload-time = 2025-10-15T15:14:28Z, size = 247261, hashes = { sha256 = "5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2" } }, + { url = "https://files.pythonhosted.org/packages/7b/d8/3a9e2db19d94d65771d0f2e21a9ea587d11b831332a73622f901157cc24b/coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", upload-time = 2025-10-15T15:14:30Z, size = 247092, hashes = { sha256 = "f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d" } }, + { url = "https://files.pythonhosted.org/packages/b3/b1/bbca3c472544f9e2ad2d5116b2379732957048be4b93a9c543fcd0207e5f/coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T15:14:32Z, size = 248755, hashes = { sha256 = "cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4" } }, + { url = "https://files.pythonhosted.org/packages/89/49/638d5a45a6a0f00af53d6b637c87007eb2297042186334e9923a61aa8854/coverage-7.11.0-cp314-cp314-win32.whl", upload-time = 2025-10-15T15:14:34Z, size = 218793, hashes = { sha256 = "bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721" } }, + { url = "https://files.pythonhosted.org/packages/30/cc/b675a51f2d068adb3cdf3799212c662239b0ca27f4691d1fff81b92ea850/coverage-7.11.0-cp314-cp314-win_amd64.whl", upload-time = 2025-10-15T15:14:37Z, size = 219587, hashes = { sha256 = "3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad" } }, + { url = "https://files.pythonhosted.org/packages/93/98/5ac886876026de04f00820e5094fe22166b98dcb8b426bf6827aaf67048c/coverage-7.11.0-cp314-cp314-win_arm64.whl", upload-time = 2025-10-15T15:14:38Z, size = 218168, hashes = { sha256 = "ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479" } }, + { url = "https://files.pythonhosted.org/packages/14/d1/b4145d35b3e3ecf4d917e97fc8895bcf027d854879ba401d9ff0f533f997/coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T15:14:40Z, size = 216850, hashes = { sha256 = "f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f" } }, + { url = "https://files.pythonhosted.org/packages/ca/d1/7f645fc2eccd318369a8a9948acc447bb7c1ade2911e31d3c5620544c22b/coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:14:42Z, size = 217071, hashes = { sha256 = "05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e" } }, + { url = "https://files.pythonhosted.org/packages/54/7d/64d124649db2737ceced1dfcbdcb79898d5868d311730f622f8ecae84250/coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", upload-time = 2025-10-15T15:14:44Z, size = 258570, hashes = { sha256 = "cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44" } }, + { url = "https://files.pythonhosted.org/packages/6c/3f/6f5922f80dc6f2d8b2c6f974835c43f53eb4257a7797727e6ca5b7b2ec1f/coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-15T15:14:46Z, size = 260738, hashes = { sha256 = "314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3" } }, + { url = "https://files.pythonhosted.org/packages/0e/5f/9e883523c4647c860b3812b417a2017e361eca5b635ee658387dc11b13c1/coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:14:48Z, size = 262994, hashes = { sha256 = "630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b" } }, + { url = "https://files.pythonhosted.org/packages/07/bb/43b5a8e94c09c8bf51743ffc65c4c841a4ca5d3ed191d0a6919c379a1b83/coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-10-15T15:14:50Z, size = 257282, hashes = { sha256 = "e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d" } }, + { url = "https://files.pythonhosted.org/packages/aa/e5/0ead8af411411330b928733e1d201384b39251a5f043c1612970310e8283/coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T15:14:52Z, size = 260430, hashes = { sha256 = "c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2" } }, + { url = "https://files.pythonhosted.org/packages/ae/66/03dd8bb0ba5b971620dcaac145461950f6d8204953e535d2b20c6b65d729/coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", upload-time = 2025-10-15T15:14:54Z, size = 258190, hashes = { sha256 = "ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e" } }, + { url = "https://files.pythonhosted.org/packages/45/ae/28a9cce40bf3174426cb2f7e71ee172d98e7f6446dff936a7ccecee34b14/coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", upload-time = 2025-10-15T15:14:56Z, size = 256658, hashes = { sha256 = "e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996" } }, + { url = "https://files.pythonhosted.org/packages/5c/7c/3a44234a8599513684bfc8684878fd7b126c2760f79712bb78c56f19efc4/coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T15:14:58Z, size = 259342, hashes = { sha256 = "424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11" } }, + { url = "https://files.pythonhosted.org/packages/e1/e6/0108519cba871af0351725ebdb8660fd7a0fe2ba3850d56d32490c7d9b4b/coverage-7.11.0-cp314-cp314t-win32.whl", upload-time = 2025-10-15T15:15:00Z, size = 219568, hashes = { sha256 = "4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73" } }, + { url = "https://files.pythonhosted.org/packages/c9/76/44ba876e0942b4e62fdde23ccb029ddb16d19ba1bef081edd00857ba0b16/coverage-7.11.0-cp314-cp314t-win_amd64.whl", upload-time = 2025-10-15T15:15:02Z, size = 220687, hashes = { sha256 = "b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547" } }, + { url = "https://files.pythonhosted.org/packages/b9/0c/0df55ecb20d0d0ed5c322e10a441775e1a3a5d78c60f0c4e1abfe6fcf949/coverage-7.11.0-cp314-cp314t-win_arm64.whl", upload-time = 2025-10-15T15:15:04Z, size = 218711, hashes = { sha256 = "b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3" } }, + { url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", upload-time = 2025-10-15T15:15:06Z, size = 207761, hashes = { sha256 = "4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68" } }, +] + +[[packages]] name = "cryptography" -version = "44.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/53/c776d80e9d26441bb3868457909b4e74dd9ccabd182e10b2b0ae7a07e265/cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88", size = 6670281 }, - { url = "https://files.pythonhosted.org/packages/6a/06/af2cf8d56ef87c77319e9086601bef621bedf40f6f59069e1b6d1ec498c5/cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137", size = 3959305 }, - { url = "https://files.pythonhosted.org/packages/ae/01/80de3bec64627207d030f47bf3536889efee8913cd363e78ca9a09b13c8e/cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c", size = 4171040 }, - { url = "https://files.pythonhosted.org/packages/bd/48/bb16b7541d207a19d9ae8b541c70037a05e473ddc72ccb1386524d4f023c/cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76", size = 3963411 }, - { url = "https://files.pythonhosted.org/packages/42/b2/7d31f2af5591d217d71d37d044ef5412945a8a8e98d5a2a8ae4fd9cd4489/cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359", size = 3689263 }, - { url = "https://files.pythonhosted.org/packages/25/50/c0dfb9d87ae88ccc01aad8eb93e23cfbcea6a6a106a9b63a7b14c1f93c75/cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43", size = 4196198 }, - { url = "https://files.pythonhosted.org/packages/66/c9/55c6b8794a74da652690c898cb43906310a3e4e4f6ee0b5f8b3b3e70c441/cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01", size = 3966502 }, - { url = "https://files.pythonhosted.org/packages/b6/f7/7cb5488c682ca59a02a32ec5f975074084db4c983f849d47b7b67cc8697a/cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d", size = 4196173 }, - { url = "https://files.pythonhosted.org/packages/d2/0b/2f789a8403ae089b0b121f8f54f4a3e5228df756e2146efdf4a09a3d5083/cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904", size = 4087713 }, - { url = "https://files.pythonhosted.org/packages/1d/aa/330c13655f1af398fc154089295cf259252f0ba5df93b4bc9d9c7d7f843e/cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44", size = 4299064 }, - { url = "https://files.pythonhosted.org/packages/10/a8/8c540a421b44fd267a7d58a1fd5f072a552d72204a3f08194f98889de76d/cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d", size = 2773887 }, - { url = "https://files.pythonhosted.org/packages/b9/0d/c4b1657c39ead18d76bbd122da86bd95bdc4095413460d09544000a17d56/cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d", size = 3209737 }, - { url = "https://files.pythonhosted.org/packages/34/a3/ad08e0bcc34ad436013458d7528e83ac29910943cea42ad7dd4141a27bbb/cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f", size = 6673501 }, - { url = "https://files.pythonhosted.org/packages/b1/f0/7491d44bba8d28b464a5bc8cc709f25a51e3eac54c0a4444cf2473a57c37/cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759", size = 3960307 }, - { url = "https://files.pythonhosted.org/packages/f7/c8/e5c5d0e1364d3346a5747cdcd7ecbb23ca87e6dea4f942a44e88be349f06/cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645", size = 4170876 }, - { url = "https://files.pythonhosted.org/packages/73/96/025cb26fc351d8c7d3a1c44e20cf9a01e9f7cf740353c9c7a17072e4b264/cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2", size = 3964127 }, - { url = "https://files.pythonhosted.org/packages/01/44/eb6522db7d9f84e8833ba3bf63313f8e257729cf3a8917379473fcfd6601/cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54", size = 3689164 }, - { url = "https://files.pythonhosted.org/packages/68/fb/d61a4defd0d6cee20b1b8a1ea8f5e25007e26aeb413ca53835f0cae2bcd1/cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93", size = 4198081 }, - { url = "https://files.pythonhosted.org/packages/1b/50/457f6911d36432a8811c3ab8bd5a6090e8d18ce655c22820994913dd06ea/cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c", size = 3967716 }, - { url = "https://files.pythonhosted.org/packages/35/6e/dca39d553075980ccb631955c47b93d87d27f3596da8d48b1ae81463d915/cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f", size = 4197398 }, - { url = "https://files.pythonhosted.org/packages/9b/9d/d1f2fe681eabc682067c66a74addd46c887ebacf39038ba01f8860338d3d/cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5", size = 4087900 }, - { url = "https://files.pythonhosted.org/packages/c4/f5/3599e48c5464580b73b236aafb20973b953cd2e7b44c7c2533de1d888446/cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b", size = 4301067 }, - { url = "https://files.pythonhosted.org/packages/a7/6c/d2c48c8137eb39d0c193274db5c04a75dab20d2f7c3f81a7dcc3a8897701/cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028", size = 2775467 }, - { url = "https://files.pythonhosted.org/packages/c9/ad/51f212198681ea7b0deaaf8846ee10af99fba4e894f67b353524eab2bbe5/cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334", size = 3210375 }, -] - -[[package]] +version = "46.0.3" +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", upload-time = 2025-10-15T23:18:31Z, size = 749258, hashes = { sha256 = "a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", upload-time = 2025-10-15T23:16:52Z, size = 7225004, hashes = { sha256 = "109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a" } }, + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-10-15T23:16:54Z, size = 4296667, hashes = { sha256 = "09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc" } }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-10-15T23:16:56Z, size = 4450807, hashes = { sha256 = "01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d" } }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T23:16:58Z, size = 4299615, hashes = { sha256 = "6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb" } }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", upload-time = 2025-10-15T23:17:00Z, size = 4016800, hashes = { sha256 = "e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849" } }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", upload-time = 2025-10-15T23:17:01Z, size = 4984707, hashes = { sha256 = "5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8" } }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T23:17:04Z, size = 4482541, hashes = { sha256 = "a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec" } }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", upload-time = 2025-10-15T23:17:05Z, size = 4299464, hashes = { sha256 = "549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91" } }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", upload-time = 2025-10-15T23:17:07Z, size = 4950838, hashes = { sha256 = "c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e" } }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", upload-time = 2025-10-15T23:17:09Z, size = 4481596, hashes = { sha256 = "10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926" } }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T23:17:11Z, size = 4426782, hashes = { sha256 = "0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71" } }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T23:17:12Z, size = 4698381, hashes = { sha256 = "a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac" } }, + { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", upload-time = 2025-10-15T23:17:14Z, size = 3055988, hashes = { sha256 = "f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018" } }, + { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", upload-time = 2025-10-15T23:17:16Z, size = 3514451, hashes = { sha256 = "a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb" } }, + { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", upload-time = 2025-10-15T23:17:18Z, size = 2928007, hashes = { sha256 = "5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c" } }, + { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", upload-time = 2025-10-15T23:17:19Z, size = 7158012, hashes = { sha256 = "00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217" } }, + { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-10-15T23:17:21Z, size = 4288728, hashes = { sha256 = "c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5" } }, + { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-10-15T23:17:23Z, size = 4435078, hashes = { sha256 = "39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715" } }, + { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T23:17:24Z, size = 4293460, hashes = { sha256 = "db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54" } }, + { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", upload-time = 2025-10-15T23:17:26Z, size = 3995237, hashes = { sha256 = "78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459" } }, + { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", upload-time = 2025-10-15T23:17:28Z, size = 4967344, hashes = { sha256 = "dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422" } }, + { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T23:17:29Z, size = 4466564, hashes = { sha256 = "6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7" } }, + { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", upload-time = 2025-10-15T23:17:31Z, size = 4292415, hashes = { sha256 = "23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044" } }, + { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", upload-time = 2025-10-15T23:17:33Z, size = 4931457, hashes = { sha256 = "b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665" } }, + { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", upload-time = 2025-10-15T23:17:35Z, size = 4466074, hashes = { sha256 = "50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3" } }, + { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T23:17:37Z, size = 4420569, hashes = { sha256 = "22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20" } }, + { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T23:17:39Z, size = 4681941, hashes = { sha256 = "d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de" } }, + { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", upload-time = 2025-10-15T23:17:40Z, size = 3022339, hashes = { sha256 = "8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914" } }, + { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", upload-time = 2025-10-15T23:17:42Z, size = 3494315, hashes = { sha256 = "760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db" } }, + { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", upload-time = 2025-10-15T23:17:44Z, size = 2919331, hashes = { sha256 = "516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21" } }, + { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", upload-time = 2025-10-15T23:17:46Z, size = 7218248, hashes = { sha256 = "cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936" } }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-10-15T23:17:48Z, size = 4294089, hashes = { sha256 = "4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683" } }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-10-15T23:17:49Z, size = 4440029, hashes = { sha256 = "15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d" } }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T23:17:51Z, size = 4297222, hashes = { sha256 = "46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0" } }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", upload-time = 2025-10-15T23:17:52Z, size = 4012280, hashes = { sha256 = "10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc" } }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", upload-time = 2025-10-15T23:17:54Z, size = 4978958, hashes = { sha256 = "36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3" } }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T23:17:56Z, size = 4473714, hashes = { sha256 = "1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971" } }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", upload-time = 2025-10-15T23:17:58Z, size = 4296970, hashes = { sha256 = "b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac" } }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", upload-time = 2025-10-15T23:18:00Z, size = 4940236, hashes = { sha256 = "71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04" } }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", upload-time = 2025-10-15T23:18:02Z, size = 4472642, hashes = { sha256 = "402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506" } }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T23:18:04Z, size = 4423126, hashes = { sha256 = "ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963" } }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T23:18:06Z, size = 4686573, hashes = { sha256 = "3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4" } }, + { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", upload-time = 2025-10-15T23:18:08Z, size = 3036695, hashes = { sha256 = "6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df" } }, + { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", upload-time = 2025-10-15T23:18:10Z, size = 3501720, hashes = { sha256 = "416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f" } }, + { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", upload-time = 2025-10-15T23:18:12Z, size = 2918740, hashes = { sha256 = "d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372" } }, + { url = "https://files.pythonhosted.org/packages/d9/cd/1a8633802d766a0fa46f382a77e096d7e209e0817892929655fe0586ae32/cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", upload-time = 2025-10-15T23:18:13Z, size = 3689163, hashes = { sha256 = "a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32" } }, + { url = "https://files.pythonhosted.org/packages/4c/59/6b26512964ace6480c3e54681a9859c974172fb141c38df11eadd8416947/cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", upload-time = 2025-10-15T23:18:15Z, size = 3429474, hashes = { sha256 = "e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c" } }, + { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", upload-time = 2025-10-15T23:18:17Z, size = 3698132, hashes = { sha256 = "7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea" } }, + { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T23:18:18Z, size = 4243992, hashes = { sha256 = "191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b" } }, + { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T23:18:20Z, size = 4409944, hashes = { sha256 = "c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb" } }, + { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", upload-time = 2025-10-15T23:18:22Z, size = 4242957, hashes = { sha256 = "9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717" } }, + { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", upload-time = 2025-10-15T23:18:24Z, size = 4409447, hashes = { sha256 = "94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9" } }, + { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", upload-time = 2025-10-15T23:18:26Z, size = 3438528, hashes = { sha256 = "6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c" } }, +] + +[[packages]] 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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", upload-time = 2023-12-24T09:54:32Z, size = 60722, hashes = { sha256 = "2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", upload-time = 2023-12-24T09:54:30Z, size = 20277, hashes = { sha256 = "7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2" } }] -[[package]] +[[packages]] +name = "fastapi" +version = "0.119.0" +sdist = { url = "https://files.pythonhosted.org/packages/0a/f9/5c5bcce82a7997cc0eb8c47b7800f862f6b56adc40486ed246e5010d443b/fastapi-0.119.0.tar.gz", upload-time = 2025-10-11T17:13:40Z, size = 336756, hashes = { sha256 = "451082403a2c1f0b99c6bd57c09110ed5463856804c8078d38e5a1f1035dbbb7" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/ce/70/584c4d7cad80f5e833715c0a29962d7c93b4d18eed522a02981a6d1b6ee5/fastapi-0.119.0-py3-none-any.whl", upload-time = 2025-10-11T17:13:39Z, size = 107095, hashes = { sha256 = "90a2e49ed19515320abb864df570dd766be0662c5d577688f1600170f7f73cf2" } }] + +[[packages]] name = "filelock" -version = "3.18.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, -] +version = "3.20.0" +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", upload-time = 2025-10-08T18:03:50Z, size = 18922, hashes = { sha256 = "711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", upload-time = 2025-10-08T18:03:48Z, size = 16054, hashes = { sha256 = "339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2" } }] -[[package]] +[[packages]] name = "fsspec" -version = "2025.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/d8/8425e6ba5fcec61a1d16e41b1b71d2bf9344f1fe48012c2b48b9620feae5/fsspec-2025.3.2.tar.gz", hash = "sha256:e52c77ef398680bbd6a98c0e628fbc469491282981209907bbc8aea76a04fdc6", size = 299281 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/4b/e0cfc1a6f17e990f3e64b7d941ddc4acdc7b19d6edd51abf495f32b1a9e4/fsspec-2025.3.2-py3-none-any.whl", hash = "sha256:2daf8dc3d1dfa65b6aa37748d112773a7a08416f6c70d96b264c96476ecaf711", size = 194435 }, -] +version = "2025.9.0" +sdist = { url = "https://files.pythonhosted.org/packages/de/e0/bab50af11c2d75c9c4a2a26a5254573c0bd97cea152254401510950486fa/fsspec-2025.9.0.tar.gz", upload-time = 2025-09-02T19:10:49Z, size = 304847, hashes = { sha256 = "19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", upload-time = 2025-09-02T19:10:47Z, size = 199289, hashes = { sha256 = "530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7" } }] -[[package]] +[[packages]] +name = "ghp-import" +version = "2.1.0" +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", upload-time = 2022-05-02T15:47:16Z, size = 10943, hashes = { sha256 = "9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", upload-time = 2022-05-02T15:47:14Z, size = 11034, hashes = { sha256 = "8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619" } }] + +[[packages]] +name = "grpcio" +version = "1.75.1" +sdist = { url = "https://files.pythonhosted.org/packages/9d/f7/8963848164c7604efb3a3e6ee457fdb3a469653e19002bd24742473254f8/grpcio-1.75.1.tar.gz", upload-time = 2025-09-26T09:03:36Z, size = 12731327, hashes = { sha256 = "3e81d89ece99b9ace23a6916880baca613c03a799925afb2857887efa8b1b3d2" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/57/89fd829fb00a6d0bee3fbcb2c8a7aa0252d908949b6ab58bfae99d39d77e/grpcio-1.75.1-cp310-cp310-linux_armv7l.whl", upload-time = 2025-09-26T09:00:52Z, size = 5705534, hashes = { sha256 = "1712b5890b22547dd29f3215c5788d8fc759ce6dd0b85a6ba6e2731f2d04c088" } }, + { url = "https://files.pythonhosted.org/packages/76/dd/2f8536e092551cf804e96bcda79ecfbc51560b214a0f5b7ebc253f0d4664/grpcio-1.75.1-cp310-cp310-macosx_11_0_universal2.whl", upload-time = 2025-09-26T09:00:59Z, size = 11484103, hashes = { sha256 = "8d04e101bba4b55cea9954e4aa71c24153ba6182481b487ff376da28d4ba46cf" } }, + { url = "https://files.pythonhosted.org/packages/9a/3d/affe2fb897804c98d56361138e73786af8f4dd876b9d9851cfe6342b53c8/grpcio-1.75.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-26T09:01:03Z, size = 6289953, hashes = { sha256 = "683cfc70be0c1383449097cba637317e4737a357cfc185d887fd984206380403" } }, + { url = "https://files.pythonhosted.org/packages/87/aa/0f40b7f47a0ff10d7e482bc3af22dac767c7ff27205915f08962d5ca87a2/grpcio-1.75.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", upload-time = 2025-09-26T09:01:07Z, size = 6949785, hashes = { sha256 = "491444c081a54dcd5e6ada57314321ae526377f498d4aa09d975c3241c5b9e1c" } }, + { url = "https://files.pythonhosted.org/packages/a5/45/b04407e44050781821c84f26df71b3f7bc469923f92f9f8bc27f1406dbcc/grpcio-1.75.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-26T09:01:11Z, size = 6465708, hashes = { sha256 = "ce08d4e112d0d38487c2b631ec8723deac9bc404e9c7b1011426af50a79999e4" } }, + { url = "https://files.pythonhosted.org/packages/09/3e/4ae3ec0a4d20dcaafbb6e597defcde06399ccdc5b342f607323f3b47f0a3/grpcio-1.75.1-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-09-26T09:01:14Z, size = 7100912, hashes = { sha256 = "5a2acda37fc926ccc4547977ac3e56b1df48fe200de968e8c8421f6e3093df6c" } }, + { url = "https://files.pythonhosted.org/packages/34/3f/a9085dab5c313bb0cb853f222d095e2477b9b8490a03634cdd8d19daa5c3/grpcio-1.75.1-cp310-cp310-musllinux_1_2_i686.whl", upload-time = 2025-09-26T09:01:17Z, size = 8042497, hashes = { sha256 = "745c5fe6bf05df6a04bf2d11552c7d867a2690759e7ab6b05c318a772739bd75" } }, + { url = "https://files.pythonhosted.org/packages/c3/87/ea54eba931ab9ed3f999ba95f5d8d01a20221b664725bab2fe93e3dee848/grpcio-1.75.1-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-09-26T09:01:20Z, size = 7493284, hashes = { sha256 = "259526a7159d39e2db40d566fe3e8f8e034d0fb2db5bf9c00e09aace655a4c2b" } }, + { url = "https://files.pythonhosted.org/packages/b7/5e/287f1bf1a998f4ac46ef45d518de3b5da08b4e86c7cb5e1108cee30b0282/grpcio-1.75.1-cp310-cp310-win32.whl", upload-time = 2025-09-26T09:01:23Z, size = 3950809, hashes = { sha256 = "f4b29b9aabe33fed5df0a85e5f13b09ff25e2c05bd5946d25270a8bd5682dac9" } }, + { url = "https://files.pythonhosted.org/packages/a4/a2/3cbfc06a4ec160dc77403b29ecb5cf76ae329eb63204fea6a7c715f1dfdb/grpcio-1.75.1-cp310-cp310-win_amd64.whl", upload-time = 2025-09-26T09:01:25Z, size = 4644704, hashes = { sha256 = "cf2e760978dcce7ff7d465cbc7e276c3157eedc4c27aa6de7b594c7a295d3d61" } }, + { url = "https://files.pythonhosted.org/packages/0c/3c/35ca9747473a306bfad0cee04504953f7098527cd112a4ab55c55af9e7bd/grpcio-1.75.1-cp311-cp311-linux_armv7l.whl", upload-time = 2025-09-26T09:01:28Z, size = 5709761, hashes = { sha256 = "573855ca2e58e35032aff30bfbd1ee103fbcf4472e4b28d4010757700918e326" } }, + { url = "https://files.pythonhosted.org/packages/c9/2c/ecbcb4241e4edbe85ac2663f885726fea0e947767401288b50d8fdcb9200/grpcio-1.75.1-cp311-cp311-macosx_11_0_universal2.whl", upload-time = 2025-09-26T09:01:31Z, size = 11496691, hashes = { sha256 = "6a4996a2c8accc37976dc142d5991adf60733e223e5c9a2219e157dc6a8fd3a2" } }, + { url = "https://files.pythonhosted.org/packages/81/40/bc07aee2911f0d426fa53fe636216100c31a8ea65a400894f280274cb023/grpcio-1.75.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-26T09:01:34Z, size = 6296084, hashes = { sha256 = "b1ea1bbe77ecbc1be00af2769f4ae4a88ce93be57a4f3eebd91087898ed749f9" } }, + { url = "https://files.pythonhosted.org/packages/b8/d1/10c067f6c67396cbf46448b80f27583b5e8c4b46cdfbe18a2a02c2c2f290/grpcio-1.75.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", upload-time = 2025-09-26T09:01:36Z, size = 6950403, hashes = { sha256 = "e5b425aee54cc5e3e3c58f00731e8a33f5567965d478d516d35ef99fd648ab68" } }, + { url = "https://files.pythonhosted.org/packages/3f/42/5f628abe360b84dfe8dd8f32be6b0606dc31dc04d3358eef27db791ea4d5/grpcio-1.75.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-26T09:01:39Z, size = 6470166, hashes = { sha256 = "0049a7bf547dafaeeb1db17079ce79596c298bfe308fc084d023c8907a845b9a" } }, + { url = "https://files.pythonhosted.org/packages/c3/93/a24035080251324019882ee2265cfde642d6476c0cf8eb207fc693fcebdc/grpcio-1.75.1-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-09-26T09:01:41Z, size = 7107828, hashes = { sha256 = "5b8ea230c7f77c0a1a3208a04a1eda164633fb0767b4cefd65a01079b65e5b1f" } }, + { url = "https://files.pythonhosted.org/packages/e4/f8/d18b984c1c9ba0318e3628dbbeb6af77a5007f02abc378c845070f2d3edd/grpcio-1.75.1-cp311-cp311-musllinux_1_2_i686.whl", upload-time = 2025-09-26T09:01:45Z, size = 8045421, hashes = { sha256 = "36990d629c3c9fb41e546414e5af52d0a7af37ce7113d9682c46d7e2919e4cca" } }, + { url = "https://files.pythonhosted.org/packages/7e/b6/4bf9aacff45deca5eac5562547ed212556b831064da77971a4e632917da3/grpcio-1.75.1-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-09-26T09:01:49Z, size = 7503290, hashes = { sha256 = "b10ad908118d38c2453ade7ff790e5bce36580c3742919007a2a78e3a1e521ca" } }, + { url = "https://files.pythonhosted.org/packages/3b/15/d8d69d10223cb54c887a2180bd29fe5fa2aec1d4995c8821f7aa6eaf72e4/grpcio-1.75.1-cp311-cp311-win32.whl", upload-time = 2025-09-26T09:01:51Z, size = 3950631, hashes = { sha256 = "d6be2b5ee7bea656c954dcf6aa8093c6f0e6a3ef9945c99d99fcbfc88c5c0bfe" } }, + { url = "https://files.pythonhosted.org/packages/8a/40/7b8642d45fff6f83300c24eaac0380a840e5e7fe0e8d80afd31b99d7134e/grpcio-1.75.1-cp311-cp311-win_amd64.whl", upload-time = 2025-09-26T09:01:53Z, size = 4646131, hashes = { sha256 = "61c692fb05956b17dd6d1ab480f7f10ad0536dba3bc8fd4e3c7263dc244ed772" } }, + { url = "https://files.pythonhosted.org/packages/3a/81/42be79e73a50aaa20af66731c2defeb0e8c9008d9935a64dd8ea8e8c44eb/grpcio-1.75.1-cp312-cp312-linux_armv7l.whl", upload-time = 2025-09-26T09:01:55Z, size = 5668314, hashes = { sha256 = "7b888b33cd14085d86176b1628ad2fcbff94cfbbe7809465097aa0132e58b018" } }, + { url = "https://files.pythonhosted.org/packages/c5/a7/3686ed15822fedc58c22f82b3a7403d9faf38d7c33de46d4de6f06e49426/grpcio-1.75.1-cp312-cp312-macosx_11_0_universal2.whl", upload-time = 2025-09-26T09:01:57Z, size = 11476125, hashes = { sha256 = "8775036efe4ad2085975531d221535329f5dac99b6c2a854a995456098f99546" } }, + { url = "https://files.pythonhosted.org/packages/14/85/21c71d674f03345ab183c634ecd889d3330177e27baea8d5d247a89b6442/grpcio-1.75.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-26T09:02:00Z, size = 6246335, hashes = { sha256 = "bb658f703468d7fbb5dcc4037c65391b7dc34f808ac46ed9136c24fc5eeb041d" } }, + { url = "https://files.pythonhosted.org/packages/fd/db/3beb661bc56a385ae4fa6b0e70f6b91ac99d47afb726fe76aaff87ebb116/grpcio-1.75.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", upload-time = 2025-09-26T09:02:02Z, size = 6916309, hashes = { sha256 = "4b7177a1cdb3c51b02b0c0a256b0a72fdab719600a693e0e9037949efffb200b" } }, + { url = "https://files.pythonhosted.org/packages/1e/9c/eda9fe57f2b84343d44c1b66cf3831c973ba29b078b16a27d4587a1fdd47/grpcio-1.75.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-26T09:02:05Z, size = 6435419, hashes = { sha256 = "7d4fa6ccc3ec2e68a04f7b883d354d7fea22a34c44ce535a2f0c0049cf626ddf" } }, + { url = "https://files.pythonhosted.org/packages/c3/b8/090c98983e0a9d602e3f919a6e2d4e470a8b489452905f9a0fa472cac059/grpcio-1.75.1-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-09-26T09:02:07Z, size = 7064893, hashes = { sha256 = "3d86880ecaeb5b2f0a8afa63824de93adb8ebe4e49d0e51442532f4e08add7d6" } }, + { url = "https://files.pythonhosted.org/packages/ec/c0/6d53d4dbbd00f8bd81571f5478d8a95528b716e0eddb4217cc7cb45aae5f/grpcio-1.75.1-cp312-cp312-musllinux_1_2_i686.whl", upload-time = 2025-09-26T09:02:09Z, size = 8011922, hashes = { sha256 = "a8041d2f9e8a742aeae96f4b047ee44e73619f4f9d24565e84d5446c623673b6" } }, + { url = "https://files.pythonhosted.org/packages/f2/7c/48455b2d0c5949678d6982c3e31ea4d89df4e16131b03f7d5c590811cbe9/grpcio-1.75.1-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-09-26T09:02:12Z, size = 7466181, hashes = { sha256 = "3652516048bf4c314ce12be37423c79829f46efffb390ad64149a10c6071e8de" } }, + { url = "https://files.pythonhosted.org/packages/fd/12/04a0e79081e3170b6124f8cba9b6275871276be06c156ef981033f691880/grpcio-1.75.1-cp312-cp312-win32.whl", upload-time = 2025-09-26T09:02:14Z, size = 3938543, hashes = { sha256 = "44b62345d8403975513af88da2f3d5cc76f73ca538ba46596f92a127c2aea945" } }, + { url = "https://files.pythonhosted.org/packages/5f/d7/11350d9d7fb5adc73d2b0ebf6ac1cc70135577701e607407fe6739a90021/grpcio-1.75.1-cp312-cp312-win_amd64.whl", upload-time = 2025-09-26T09:02:16Z, size = 4641938, hashes = { sha256 = "b1e191c5c465fa777d4cafbaacf0c01e0d5278022082c0abbd2ee1d6454ed94d" } }, + { url = "https://files.pythonhosted.org/packages/46/74/bac4ab9f7722164afdf263ae31ba97b8174c667153510322a5eba4194c32/grpcio-1.75.1-cp313-cp313-linux_armv7l.whl", upload-time = 2025-09-26T09:02:19Z, size = 5672779, hashes = { sha256 = "3bed22e750d91d53d9e31e0af35a7b0b51367e974e14a4ff229db5b207647884" } }, + { url = "https://files.pythonhosted.org/packages/a6/52/d0483cfa667cddaa294e3ab88fd2c2a6e9dc1a1928c0e5911e2e54bd5b50/grpcio-1.75.1-cp313-cp313-macosx_11_0_universal2.whl", upload-time = 2025-09-26T09:02:22Z, size = 11470623, hashes = { sha256 = "5b8f381eadcd6ecaa143a21e9e80a26424c76a0a9b3d546febe6648f3a36a5ac" } }, + { url = "https://files.pythonhosted.org/packages/cf/e4/d1954dce2972e32384db6a30273275e8c8ea5a44b80347f9055589333b3f/grpcio-1.75.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-26T09:02:26Z, size = 6248838, hashes = { sha256 = "5bf4001d3293e3414d0cf99ff9b1139106e57c3a66dfff0c5f60b2a6286ec133" } }, + { url = "https://files.pythonhosted.org/packages/06/43/073363bf63826ba8077c335d797a8d026f129dc0912b69c42feaf8f0cd26/grpcio-1.75.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", upload-time = 2025-09-26T09:02:28Z, size = 6922663, hashes = { sha256 = "9f82ff474103e26351dacfe8d50214e7c9322960d8d07ba7fa1d05ff981c8b2d" } }, + { url = "https://files.pythonhosted.org/packages/c2/6f/076ac0df6c359117676cacfa8a377e2abcecec6a6599a15a672d331f6680/grpcio-1.75.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-26T09:02:30Z, size = 6436149, hashes = { sha256 = "0ee119f4f88d9f75414217823d21d75bfe0e6ed40135b0cbbfc6376bc9f7757d" } }, + { url = "https://files.pythonhosted.org/packages/6b/27/1d08824f1d573fcb1fa35ede40d6020e68a04391709939e1c6f4193b445f/grpcio-1.75.1-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-09-26T09:02:33Z, size = 7067989, hashes = { sha256 = "664eecc3abe6d916fa6cf8dd6b778e62fb264a70f3430a3180995bf2da935446" } }, + { url = "https://files.pythonhosted.org/packages/c6/98/98594cf97b8713feb06a8cb04eeef60b4757e3e2fb91aa0d9161da769843/grpcio-1.75.1-cp313-cp313-musllinux_1_2_i686.whl", upload-time = 2025-09-26T09:02:36Z, size = 8010717, hashes = { sha256 = "c32193fa08b2fbebf08fe08e84f8a0aad32d87c3ad42999c65e9449871b1c66e" } }, + { url = "https://files.pythonhosted.org/packages/8c/7e/bb80b1bba03c12158f9254762cdf5cced4a9bc2e8ed51ed335915a5a06ef/grpcio-1.75.1-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-09-26T09:02:38Z, size = 7463822, hashes = { sha256 = "5cebe13088b9254f6e615bcf1da9131d46cfa4e88039454aca9cb65f639bd3bc" } }, + { url = "https://files.pythonhosted.org/packages/23/1c/1ea57fdc06927eb5640f6750c697f596f26183573069189eeaf6ef86ba2d/grpcio-1.75.1-cp313-cp313-win32.whl", upload-time = 2025-09-26T09:02:40Z, size = 3938490, hashes = { sha256 = "4b4c678e7ed50f8ae8b8dbad15a865ee73ce12668b6aaf411bf3258b5bc3f970" } }, + { url = "https://files.pythonhosted.org/packages/4b/24/fbb8ff1ccadfbf78ad2401c41aceaf02b0d782c084530d8871ddd69a2d49/grpcio-1.75.1-cp313-cp313-win_amd64.whl", upload-time = 2025-09-26T09:02:42Z, size = 4642538, hashes = { sha256 = "5573f51e3f296a1bcf71e7a690c092845fb223072120f4bdb7a5b48e111def66" } }, + { url = "https://files.pythonhosted.org/packages/f2/1b/9a0a5cecd24302b9fdbcd55d15ed6267e5f3d5b898ff9ac8cbe17ee76129/grpcio-1.75.1-cp314-cp314-linux_armv7l.whl", upload-time = 2025-09-26T09:02:44Z, size = 5673319, hashes = { sha256 = "c05da79068dd96723793bffc8d0e64c45f316248417515f28d22204d9dae51c7" } }, + { url = "https://files.pythonhosted.org/packages/c6/ec/9d6959429a83fbf5df8549c591a8a52bb313976f6646b79852c4884e3225/grpcio-1.75.1-cp314-cp314-macosx_11_0_universal2.whl", upload-time = 2025-09-26T09:02:47Z, size = 11480347, hashes = { sha256 = "06373a94fd16ec287116a825161dca179a0402d0c60674ceeec8c9fba344fe66" } }, + { url = "https://files.pythonhosted.org/packages/09/7a/26da709e42c4565c3d7bf999a9569da96243ce34a8271a968dee810a7cf1/grpcio-1.75.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-26T09:02:50Z, size = 6254706, hashes = { sha256 = "4484f4b7287bdaa7a5b3980f3c7224c3c622669405d20f69549f5fb956ad0421" } }, + { url = "https://files.pythonhosted.org/packages/f1/08/dcb26a319d3725f199c97e671d904d84ee5680de57d74c566a991cfab632/grpcio-1.75.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", upload-time = 2025-09-26T09:02:52Z, size = 6922501, hashes = { sha256 = "2720c239c1180eee69f7883c1d4c83fc1a495a2535b5fa322887c70bf02b16e8" } }, + { url = "https://files.pythonhosted.org/packages/78/66/044d412c98408a5e23cb348845979a2d17a2e2b6c3c34c1ec91b920f49d0/grpcio-1.75.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-26T09:02:55Z, size = 6437492, hashes = { sha256 = "07a554fa31c668cf0e7a188678ceeca3cb8fead29bbe455352e712ec33ca701c" } }, + { url = "https://files.pythonhosted.org/packages/4e/9d/5e3e362815152aa1afd8b26ea613effa005962f9da0eec6e0e4527e7a7d1/grpcio-1.75.1-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-09-26T09:02:58Z, size = 7081061, hashes = { sha256 = "3e71a2105210366bfc398eef7f57a664df99194f3520edb88b9c3a7e46ee0d64" } }, + { url = "https://files.pythonhosted.org/packages/1e/1a/46615682a19e100f46e31ddba9ebc297c5a5ab9ddb47b35443ffadb8776c/grpcio-1.75.1-cp314-cp314-musllinux_1_2_i686.whl", upload-time = 2025-09-26T09:03:00Z, size = 8010849, hashes = { sha256 = "8679aa8a5b67976776d3c6b0521e99d1c34db8a312a12bcfd78a7085cb9b604e" } }, + { url = "https://files.pythonhosted.org/packages/67/8e/3204b94ac30b0f675ab1c06540ab5578660dc8b690db71854d3116f20d00/grpcio-1.75.1-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-09-26T09:03:03Z, size = 7464478, hashes = { sha256 = "aad1c774f4ebf0696a7f148a56d39a3432550612597331792528895258966dc0" } }, + { url = "https://files.pythonhosted.org/packages/b7/97/2d90652b213863b2cf466d9c1260ca7e7b67a16780431b3eb1d0420e3d5b/grpcio-1.75.1-cp314-cp314-win32.whl", upload-time = 2025-09-26T09:03:05Z, size = 4012672, hashes = { sha256 = "62ce42d9994446b307649cb2a23335fa8e927f7ab2cbf5fcb844d6acb4d85f9c" } }, + { url = "https://files.pythonhosted.org/packages/f9/df/e2e6e9fc1c985cd1a59e6996a05647c720fe8a03b92f5ec2d60d366c531e/grpcio-1.75.1-cp314-cp314-win_amd64.whl", upload-time = 2025-09-26T09:03:07Z, size = 4772475, hashes = { sha256 = "f86e92275710bea3000cb79feca1762dc0ad3b27830dd1a74e82ab321d4ee464" } }, + { url = "https://files.pythonhosted.org/packages/8f/e2/33efd823a879dc7b60c10192df1900ee5c200f8e782663a41a3b2aecd143/grpcio-1.75.1-cp39-cp39-linux_armv7l.whl", upload-time = 2025-09-26T09:03:10Z, size = 5706679, hashes = { sha256 = "c09fba33327c3ac11b5c33dbdd8218eef8990d78f83b1656d628831812a8c0fb" } }, + { url = "https://files.pythonhosted.org/packages/5f/13/17e39ee4897f1cd12dd463e863b830e64643b13e9a4af5062b4a6f0790be/grpcio-1.75.1-cp39-cp39-macosx_11_0_universal2.whl", upload-time = 2025-09-26T09:03:12Z, size = 11490271, hashes = { sha256 = "7e21400b037be29545704889e72e586c238e346dcb2d08d8a7288d16c883a9ec" } }, + { url = "https://files.pythonhosted.org/packages/77/90/b80e75f8cce758425b2772742eed4e9db765a965d902ba4b7f239b2513de/grpcio-1.75.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-26T09:03:16Z, size = 6291926, hashes = { sha256 = "c12121e509b9f8b0914d10054d24120237d19e870b1cd82acbb8a9b9ddd198a3" } }, + { url = "https://files.pythonhosted.org/packages/40/5f/e6033d8f99063350e20873a46225468b73045b9ef2c8cba73d66a87c3fd5/grpcio-1.75.1-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", upload-time = 2025-09-26T09:03:18Z, size = 6950040, hashes = { sha256 = "73577a93e692b3474b1bfe84285d098de36705dbd838bb4d6a056d326e4dc880" } }, + { url = "https://files.pythonhosted.org/packages/01/12/34076c079b45af5aed40f037fffe388d7fbe90dd539ed01e4744c926d227/grpcio-1.75.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-26T09:03:21Z, size = 6465780, hashes = { sha256 = "e19e7dfa0d7ca7dea22be464339e18ac608fd75d88c56770c646cdabe54bc724" } }, + { url = "https://files.pythonhosted.org/packages/e4/c5/ee6fd69a9f6e7288d04da010ad7480a0566d2aac81097ff4dafbc5ffa9b6/grpcio-1.75.1-cp39-cp39-musllinux_1_2_aarch64.whl", upload-time = 2025-09-26T09:03:23Z, size = 7098308, hashes = { sha256 = "4e1c28f51c1cf67eccdfc1065e8e866c9ed622f09773ca60947089c117f848a1" } }, + { url = "https://files.pythonhosted.org/packages/78/32/f2be13f13035361768923159fe20470a7d22db2c7c692b952e21284f56e5/grpcio-1.75.1-cp39-cp39-musllinux_1_2_i686.whl", upload-time = 2025-09-26T09:03:26Z, size = 8042268, hashes = { sha256 = "030a6164bc2ca726052778c0cf8e3249617a34e368354f9e6107c27ad4af8c28" } }, + { url = "https://files.pythonhosted.org/packages/e7/2d/1bb0572f0a2eaab100b4635c6c2cd0d37e3cda5554037e3f90b1bc428d56/grpcio-1.75.1-cp39-cp39-musllinux_1_2_x86_64.whl", upload-time = 2025-09-26T09:03:28Z, size = 7491470, hashes = { sha256 = "67697efef5a98d46d5db7b1720fa4043536f8b8e5072a5d61cfca762f287e939" } }, + { url = "https://files.pythonhosted.org/packages/aa/e0/1e962dcb64019bbd87eedcfacdedb83af0f66da01f2f6e03d69b0aa1b7f0/grpcio-1.75.1-cp39-cp39-win32.whl", upload-time = 2025-09-26T09:03:31Z, size = 3951697, hashes = { sha256 = "52015cf73eb5d76f6404e0ce0505a69b51fd1f35810b3a01233b34b10baafb41" } }, + { url = "https://files.pythonhosted.org/packages/87/bc/47fb3aaa77e7d657999937ec1026beba9e37f3199599fe510f762d31da97/grpcio-1.75.1-cp39-cp39-win_amd64.whl", upload-time = 2025-09-26T09:03:34Z, size = 4645764, hashes = { sha256 = "9fe51e4a1f896ea84ac750900eae34d9e9b896b5b1e4a30b02dc31ad29f36383" } }, +] + +[[packages]] +name = "grpcio-tools" +version = "1.75.1" +sdist = { url = "https://files.pythonhosted.org/packages/7d/76/0cd2a2bb379275c319544a3ab613dc3cea7a167503908c1b4de55f82bd9e/grpcio_tools-1.75.1.tar.gz", upload-time = 2025-09-26T09:10:11Z, size = 5390470, hashes = { sha256 = "bb78960cf3d58941e1fec70cbdaccf255918beed13c34112a6915a6d8facebd1" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/b7/7d1b0b7669f993a6a393083a876937f478ca034c283eb23baf6720d8c85a/grpcio_tools-1.75.1-cp310-cp310-linux_armv7l.whl", upload-time = 2025-09-26T09:07:44Z, size = 2545419, hashes = { sha256 = "ae0f04d5ec8b8e13476bf516a08fc1de4e58c6bf79f99123a6b964ca7d02c790" } }, + { url = "https://files.pythonhosted.org/packages/3e/c0/db5d052d1ba5e859c833d1366960d784c0b44c8330012717aeaa123b6b9f/grpcio_tools-1.75.1-cp310-cp310-macosx_11_0_universal2.whl", upload-time = 2025-09-26T09:07:50Z, size = 5841650, hashes = { sha256 = "24a881ad7292e904fc256892b647da17d9137ef2e72faf8b7c8e515314ad1377" } }, + { url = "https://files.pythonhosted.org/packages/af/13/ab49230ef106f2b9de156a813bc14049e6fd4fe9c26fa0cde496f0e86a09/grpcio_tools-1.75.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-26T09:07:52Z, size = 2591560, hashes = { sha256 = "1b5810ace274dba12ecfac69ac32c8047c6ee0200a23274cb4885ed4187271f8" } }, + { url = "https://files.pythonhosted.org/packages/29/fd/1fd3069fb0559c2f90d85b0fd3a73adc3f63966c6300fee01e4e52740229/grpcio_tools-1.75.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", upload-time = 2025-09-26T09:07:55Z, size = 2904895, hashes = { sha256 = "ab33993288b97b1180e092fa447a8ce00fbc8c59d67b23553245b88d14fe36bb" } }, + { url = "https://files.pythonhosted.org/packages/d6/51/e58fae40132a4589819c388333545a33a89f91b8affcac45623ace9ca659/grpcio_tools-1.75.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-26T09:07:56Z, size = 2656151, hashes = { sha256 = "4cac693621043ef11d3ab2318e811d919779f8cd5011ba8e37f44c178c831d94" } }, + { url = "https://files.pythonhosted.org/packages/39/c9/a33736c2a8ceee39991f2c9f67a426ab799c6caf09145120bae6080428d5/grpcio_tools-1.75.1-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-09-26T09:07:58Z, size = 3105152, hashes = { sha256 = "a09cd5d267b296af67116fe098633ad770bc8c19831a5f3c896f65fad90c1064" } }, + { url = "https://files.pythonhosted.org/packages/ac/71/2f09cbe321f057a47a8ae4dacf004bbe8a171fd712b8f7f689ec7b2f1c49/grpcio_tools-1.75.1-cp310-cp310-musllinux_1_2_i686.whl", upload-time = 2025-09-26T09:08:00Z, size = 3654551, hashes = { sha256 = "dff4bcb4d16cf9ef745c1984394ed15187e6c23d73d71377377deaf443d11358" } }, + { url = "https://files.pythonhosted.org/packages/05/54/91481a5b96cab2a81326ab1041fd7cfc6b6ce0cd82bab14ebcdb6ed78d4e/grpcio_tools-1.75.1-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-09-26T09:08:02Z, size = 3322220, hashes = { sha256 = "16d5986b37e2a9203f85e456c7ff8705b932718021d408adfe4a79e0f4d95949" } }, + { url = "https://files.pythonhosted.org/packages/a2/07/272955f15a35ef0069ebe17a5fc14282c6dc6690edeb4dbe6a81dd2d1efb/grpcio_tools-1.75.1-cp310-cp310-win32.whl", upload-time = 2025-09-26T09:08:04Z, size = 992986, hashes = { sha256 = "3fbac14998bfadc6b9140b6339dbc5f673700ebb4d45ba0c4d4fbe0ffb8559a9" } }, + { url = "https://files.pythonhosted.org/packages/16/9a/482b05c1277b3385be7e426f000efb921ce3ae76bcb8aa4f9b9f724c58d3/grpcio_tools-1.75.1-cp310-cp310-win_amd64.whl", upload-time = 2025-09-26T09:08:06Z, size = 1157427, hashes = { sha256 = "b56e495844eb899de721eb77d9e077192bdeb40842f598481d32a8f6de3db124" } }, + { url = "https://files.pythonhosted.org/packages/45/28/71ab934662d41ded4e451d9af0ec6f9aade3525e470fdfd10bd20e588e44/grpcio_tools-1.75.1-cp311-cp311-linux_armv7l.whl", upload-time = 2025-09-26T09:08:08Z, size = 2545461, hashes = { sha256 = "f0635231feb70a9d551452829943a1a5fa651283e7a300aadc22df5ea5da696f" } }, + { url = "https://files.pythonhosted.org/packages/69/40/d90f6fdb51f51b2a518401207b3920fcfdfa996ed7bca844096f111ed839/grpcio_tools-1.75.1-cp311-cp311-macosx_11_0_universal2.whl", upload-time = 2025-09-26T09:08:11Z, size = 5842958, hashes = { sha256 = "626293296ef7e2d87ab1a80b81a55eef91883c65b59a97576099a28b9535100b" } }, + { url = "https://files.pythonhosted.org/packages/b4/b7/52e6f32fd0101e3ac9c654a6441b254ba5874f146b543b20afbcb8246947/grpcio_tools-1.75.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-26T09:08:13Z, size = 2591669, hashes = { sha256 = "071339d90f1faab332ce4919c815a10b9c3ed2c09473f550f686bf9cc148579f" } }, + { url = "https://files.pythonhosted.org/packages/0a/3c/115c59a5c0c8e9d7d99a40bac8d5e91c05b6735b3bb185265d40e9fc4346/grpcio_tools-1.75.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", upload-time = 2025-09-26T09:08:15Z, size = 2904952, hashes = { sha256 = "44195f58c052fa935b78c7438c85cbcd4b273dd685028e4f6d4d7b30d47daad1" } }, + { url = "https://files.pythonhosted.org/packages/a9/cd/d2a3583a5b1d71da88f7998f20fb5a0b6fe5bb96bb916a610c29269063b6/grpcio_tools-1.75.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-26T09:08:17Z, size = 2656311, hashes = { sha256 = "860fafdb85726029d646c99859ff7bdca5aae61b5ff038c3bd355fc1ec6b2764" } }, + { url = "https://files.pythonhosted.org/packages/aa/09/67b9215d39add550e430c9677bd43c9a315da07ab62fa3a5f44f1cf5bb75/grpcio_tools-1.75.1-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-09-26T09:08:19Z, size = 3105583, hashes = { sha256 = "4559547a0cb3d3db1b982eea87d4656036339b400f48127fef932210672fb59e" } }, + { url = "https://files.pythonhosted.org/packages/98/d7/d400b90812470f3dc2466964e62fc03592de46b5c824c82ef5303be60167/grpcio_tools-1.75.1-cp311-cp311-musllinux_1_2_i686.whl", upload-time = 2025-09-26T09:08:21Z, size = 3654677, hashes = { sha256 = "9af65a310807d7f36a8f7cddea142fe97d6dffba74444f38870272f2e5a3a06b" } }, + { url = "https://files.pythonhosted.org/packages/9c/93/edf6de71b4f936b3f09461a3286db1f902c6366c5de06ef19a8c2523034a/grpcio_tools-1.75.1-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-09-26T09:08:23Z, size = 3322147, hashes = { sha256 = "8c1de31aefc0585d2f915a7cd0994d153547495b8d79c44c58048a3ede0b65be" } }, + { url = "https://files.pythonhosted.org/packages/80/00/0f8c6204e34070e7d4f344b27e4b1b0320dfdd94574f79738a43504d182e/grpcio_tools-1.75.1-cp311-cp311-win32.whl", upload-time = 2025-09-26T09:08:24Z, size = 993388, hashes = { sha256 = "efaf95fcaa5d3ac1bcfe44ceed9e2512eb95b5c8c476569bdbbe2bee4b59c8a9" } }, + { url = "https://files.pythonhosted.org/packages/b0/ae/6f738154980f606293988a64ef4bb0ea2bb12029a4529464aac56fe2ab99/grpcio_tools-1.75.1-cp311-cp311-win_amd64.whl", upload-time = 2025-09-26T09:08:26Z, size = 1157907, hashes = { sha256 = "7cefe76fc35c825f0148d60d2294a527053d0f5dd6a60352419214a8c53223c9" } }, + { url = "https://files.pythonhosted.org/packages/ef/a7/581bb204d19a347303ed5e25b19f7d8c6365a28c242fca013d1d6d78ad7e/grpcio_tools-1.75.1-cp312-cp312-linux_armv7l.whl", upload-time = 2025-09-26T09:08:28Z, size = 2546099, hashes = { sha256 = "49b68936cf212052eeafa50b824e17731b78d15016b235d36e0d32199000b14c" } }, + { url = "https://files.pythonhosted.org/packages/9f/59/ab65998eba14ff9d292c880f6a276fe7d0571bba3bb4ddf66aca1f8438b5/grpcio_tools-1.75.1-cp312-cp312-macosx_11_0_universal2.whl", upload-time = 2025-09-26T09:08:31Z, size = 5839838, hashes = { sha256 = "08cb6e568e58b76a2178ad3b453845ff057131fff00f634d7e15dcd015cd455b" } }, + { url = "https://files.pythonhosted.org/packages/7e/65/7027f71069b4c1e8c7b46de8c46c297c9d28ef6ed4ea0161e8c82c75d1d0/grpcio_tools-1.75.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-26T09:08:33Z, size = 2592916, hashes = { sha256 = "168402ad29a249092673079cf46266936ec2fb18d4f854d96e9c5fa5708efa39" } }, + { url = "https://files.pythonhosted.org/packages/0f/84/1abfb3c679b78c7fca7524031cf9de4c4c509c441b48fd26291ac16dd1af/grpcio_tools-1.75.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", upload-time = 2025-09-26T09:08:35Z, size = 2905276, hashes = { sha256 = "bbae11c29fcf450730f021bfc14b12279f2f985e2e493ccc2f133108728261db" } }, + { url = "https://files.pythonhosted.org/packages/99/cd/7f9e05f1eddccb61bc0ead1e49eb2222441957b02ed11acfcd2f795b03a8/grpcio_tools-1.75.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-26T09:08:38Z, size = 2656424, hashes = { sha256 = "38c6c7d5d4800f636ee691cd073db1606d1a6a76424ca75c9b709436c9c20439" } }, + { url = "https://files.pythonhosted.org/packages/29/1d/8b7852771c2467728341f7b9c3ca4ebc76e4e23485c6a3e6d97a8323ad2a/grpcio_tools-1.75.1-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-09-26T09:08:40Z, size = 3108985, hashes = { sha256 = "626f6a61a8f141dde9a657775854d1c0d99509f9a2762b82aa401a635f6ec73d" } }, + { url = "https://files.pythonhosted.org/packages/c2/6a/069da89cdf2e97e4558bfceef5b60bf0ef200c443b465e7691869006dd32/grpcio_tools-1.75.1-cp312-cp312-musllinux_1_2_i686.whl", upload-time = 2025-09-26T09:08:42Z, size = 3657940, hashes = { sha256 = "f61a8334ae38d4f98c744a732b89527e5af339d17180e25fff0676060f8709b7" } }, + { url = "https://files.pythonhosted.org/packages/c3/e4/ca8dae800c084beb89e2720346f70012d36dfb9df02d8eacd518c06cf4a0/grpcio_tools-1.75.1-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-09-26T09:08:45Z, size = 3324878, hashes = { sha256 = "bd0c3fb40d89a1e24a41974e77c7331e80396ab7cde39bc396a13d6b5e2a750b" } }, + { url = "https://files.pythonhosted.org/packages/58/06/cbe923679309bf970923f4a11351ea9e485291b504d7243130fdcfdcb03f/grpcio_tools-1.75.1-cp312-cp312-win32.whl", upload-time = 2025-09-26T09:08:46Z, size = 993071, hashes = { sha256 = "004bc5327593eea48abd03be3188e757c3ca0039079587a6aac24275127cac20" } }, + { url = "https://files.pythonhosted.org/packages/7c/0c/84d6be007262c5d88a590082f3a1fe62d4b0eeefa10c6cdb3548f3663e80/grpcio_tools-1.75.1-cp312-cp312-win_amd64.whl", upload-time = 2025-09-26T09:08:48Z, size = 1157506, hashes = { sha256 = "23952692160b5fe7900653dfdc9858dc78c2c42e15c27e19ee780c8917ba6028" } }, + { url = "https://files.pythonhosted.org/packages/47/fa/624bbe1b2ccf4f6044bf3cd314fe2c35f78f702fcc2191dc65519baddca4/grpcio_tools-1.75.1-cp313-cp313-linux_armv7l.whl", upload-time = 2025-09-26T09:08:51Z, size = 2545752, hashes = { sha256 = "ca9e116aab0ecf4365fc2980f2e8ae1b22273c3847328b9a8e05cbd14345b397" } }, + { url = "https://files.pythonhosted.org/packages/b9/4c/6d884e2337feff0a656e395338019adecc3aa1daeae9d7e8eb54340d4207/grpcio_tools-1.75.1-cp313-cp313-macosx_11_0_universal2.whl", upload-time = 2025-09-26T09:08:53Z, size = 5838163, hashes = { sha256 = "9fe87a926b65eb7f41f8738b6d03677cc43185ff77a9d9b201bdb2f673f3fa1e" } }, + { url = "https://files.pythonhosted.org/packages/d1/2a/2ba7b6911a754719643ed92ae816a7f989af2be2882b9a9e1f90f4b0e882/grpcio_tools-1.75.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-26T09:08:55Z, size = 2592148, hashes = { sha256 = "45503a6094f91b3fd31c3d9adef26ac514f102086e2a37de797e220a6791ee87" } }, + { url = "https://files.pythonhosted.org/packages/88/db/fa613a45c3c7b00f905bd5ad3a93c73194724d0a2dd72adae3be32983343/grpcio_tools-1.75.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", upload-time = 2025-09-26T09:08:58Z, size = 2905215, hashes = { sha256 = "b01b60b3de67be531a39fd869d7613fa8f178aff38c05e4d8bc2fc530fa58cb5" } }, + { url = "https://files.pythonhosted.org/packages/d7/0c/ee4786972bb82f60e4f313bb2227c79c2cd20eb13c94c0263067923cfd12/grpcio_tools-1.75.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-26T09:09:00Z, size = 2656251, hashes = { sha256 = "09e2b9b9488735514777d44c1e4eda813122d2c87aad219f98d5d49b359a8eab" } }, + { url = "https://files.pythonhosted.org/packages/77/f1/cc5a50658d705d0b71ff8a4fbbfcc6279d3c95731a2ef7285e13dc40e2fe/grpcio_tools-1.75.1-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-09-26T09:09:02Z, size = 3108911, hashes = { sha256 = "55e60300e62b220fabe6f062fe69f143abaeff3335f79b22b56d86254f3c3c80" } }, + { url = "https://files.pythonhosted.org/packages/09/d8/43545f77c4918e778e90bc2c02b3462ac71cee14f29d85cdb69b089538eb/grpcio_tools-1.75.1-cp313-cp313-musllinux_1_2_i686.whl", upload-time = 2025-09-26T09:09:05Z, size = 3657021, hashes = { sha256 = "49ce00fcc6facbbf52bf376e55b8e08810cecd03dab0b3a2986d73117c6f6ee4" } }, + { url = "https://files.pythonhosted.org/packages/fc/0b/2ae5925374b66bc8df5b828eff1a5f9459349c83dae1773f0aa9858707e6/grpcio_tools-1.75.1-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-09-26T09:09:07Z, size = 3324450, hashes = { sha256 = "71e95479aea868f8c8014d9dc4267f26ee75388a0d8a552e1648cfa0b53d24b4" } }, + { url = "https://files.pythonhosted.org/packages/6e/53/9f887bacbecf892ac5b0b282477ca8cfa5b73911b04259f0d88b52e9a055/grpcio_tools-1.75.1-cp313-cp313-win32.whl", upload-time = 2025-09-26T09:09:09Z, size = 992434, hashes = { sha256 = "fff9d2297416eae8861e53154ccf70a19994e5935e6c8f58ebf431f81cbd8d12" } }, + { url = "https://files.pythonhosted.org/packages/a5/f0/9979d97002edffdc2a88e5f2e0dccea396dd4a6eab34fa2f705fe43eae2f/grpcio_tools-1.75.1-cp313-cp313-win_amd64.whl", upload-time = 2025-09-26T09:09:12Z, size = 1157069, hashes = { sha256 = "1849ddd508143eb48791e81d42ddc924c554d1b4900e06775a927573a8d4267f" } }, + { url = "https://files.pythonhosted.org/packages/a6/0b/4ff4ead293f2b016668628a240937828444094778c8037d2bbef700e9097/grpcio_tools-1.75.1-cp314-cp314-linux_armv7l.whl", upload-time = 2025-09-26T09:09:14Z, size = 2545868, hashes = { sha256 = "f281b594489184b1f9a337cdfed1fc1ddb8428f41c4b4023de81527e90b38e1e" } }, + { url = "https://files.pythonhosted.org/packages/0e/78/aa6bf73a18de5357c01ef87eea92150931586b25196fa4df197a37bae11d/grpcio_tools-1.75.1-cp314-cp314-macosx_11_0_universal2.whl", upload-time = 2025-09-26T09:09:20Z, size = 5838010, hashes = { sha256 = "becf8332f391abc62bf4eea488b63be063d76a7cf2ef00b2e36c617d9ee9216b" } }, + { url = "https://files.pythonhosted.org/packages/99/65/7eaad673bc971af45e079d3b13c20d9ba9842b8788d31953e3234c2e2cee/grpcio_tools-1.75.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-26T09:09:22Z, size = 2593170, hashes = { sha256 = "a08330f24e5cd7b39541882a95a8ba04ffb4df79e2984aa0cd01ed26dcdccf49" } }, + { url = "https://files.pythonhosted.org/packages/e4/db/57e1e29e9186c7ed223ce8a9b609d3f861c4db015efb643dfe60b403c137/grpcio_tools-1.75.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", upload-time = 2025-09-26T09:09:25Z, size = 2905167, hashes = { sha256 = "6bf3742bd8f102630072ed317d1496f31c454cd85ad19d37a68bd85bf9d5f8b9" } }, + { url = "https://files.pythonhosted.org/packages/cd/7b/894f891f3cf19812192f8bbf1e0e1c958055676ecf0a5466a350730a006d/grpcio_tools-1.75.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-26T09:09:28Z, size = 2656210, hashes = { sha256 = "f26028949474feb380460ce52d9d090d00023940c65236294a66c42ac5850e8b" } }, + { url = "https://files.pythonhosted.org/packages/99/76/8e48427da93ef243c09629969c7b5a2c59dceb674b6b623c1f5fbaa5c8c5/grpcio_tools-1.75.1-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-09-26T09:09:31Z, size = 3109226, hashes = { sha256 = "1bd68fb98bf08f11b6c3210834a14eefe585bad959bdba38e78b4ae3b04ba5bd" } }, + { url = "https://files.pythonhosted.org/packages/b3/7e/ecf71c316c2a88c2478b7c6372d0f82d05f07edbf0f31b6da613df99ec7c/grpcio_tools-1.75.1-cp314-cp314-musllinux_1_2_i686.whl", upload-time = 2025-09-26T09:09:35Z, size = 3657139, hashes = { sha256 = "f1496e21586193da62c3a73cd16f9c63c5b3efd68ff06dab96dbdfefa90d40bf" } }, + { url = "https://files.pythonhosted.org/packages/6f/f3/b2613e81da2085f40a989c0601ec9efc11e8b32fcb71b1234b64a18af830/grpcio_tools-1.75.1-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-09-26T09:09:37Z, size = 3324513, hashes = { sha256 = "14a78b1e36310cdb3516cdf9ee2726107875e0b247e2439d62fc8dc38cf793c1" } }, + { url = "https://files.pythonhosted.org/packages/9a/1f/2df4fa8634542524bc22442ffe045d41905dae62cc5dd14408b80c5ac1b8/grpcio_tools-1.75.1-cp314-cp314-win32.whl", upload-time = 2025-09-26T09:09:39Z, size = 1015283, hashes = { sha256 = "0e6f916daf222002fb98f9a6f22de0751959e7e76a24941985cc8e43cea77b50" } }, + { url = "https://files.pythonhosted.org/packages/23/4f/f27c973ff50486a70be53a3978b6b0244398ca170a4e19d91988b5295d92/grpcio_tools-1.75.1-cp314-cp314-win_amd64.whl", upload-time = 2025-09-26T09:09:42Z, size = 1189364, hashes = { sha256 = "878c3b362264588c45eba57ce088755f8b2b54893d41cc4a68cdeea62996da5c" } }, + { url = "https://files.pythonhosted.org/packages/3d/34/96ff5eb9274366bb9c4e899ad9e04dfe28af71641419b5b43dcd351da615/grpcio_tools-1.75.1-cp39-cp39-linux_armv7l.whl", upload-time = 2025-09-26T09:09:44Z, size = 2546017, hashes = { sha256 = "eca28a90020fc1596f48cf51b02e56bc3d285f7f9ebaf0493144160d69c2cae7" } }, + { url = "https://files.pythonhosted.org/packages/e8/ae/71c02c1228b687fbb1042d9028a6b510f8967dc3d33de6e4fac8a2609276/grpcio_tools-1.75.1-cp39-cp39-macosx_11_0_universal2.whl", upload-time = 2025-09-26T09:09:47Z, size = 5843755, hashes = { sha256 = "6744a14983f82e04cfd799ed779d06ee92035bb497f2d0fa84e81921a6c9c985" } }, + { url = "https://files.pythonhosted.org/packages/0b/06/f0a9be8c194ddced4137d9e18d8e6f2aa406531902d492d4745847680444/grpcio_tools-1.75.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-26T09:09:50Z, size = 2592050, hashes = { sha256 = "2a59120f17d36de6e16a058d88f2fcd255bafaccb487fea0613860a5287f77c6" } }, + { url = "https://files.pythonhosted.org/packages/04/8f/ff40122b8a666ab28631fef56f3815e53e930adf0c5bc7c8a8a4173d2206/grpcio_tools-1.75.1-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", upload-time = 2025-09-26T09:09:52Z, size = 2905103, hashes = { sha256 = "02b0c237882e45247570afdc34717ce80831184882186ef47afca9f8cac2f71c" } }, + { url = "https://files.pythonhosted.org/packages/ac/4d/b0a05270c5ceb445fe6649b08278cb4145d2971de3888ae7850ff1b6db32/grpcio_tools-1.75.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-26T09:09:54Z, size = 2656536, hashes = { sha256 = "4999ada9721ce2a0eae66bf7f2793bc6fe7a473eef4e38bb542d1e5c6d9f7d91" } }, + { url = "https://files.pythonhosted.org/packages/4e/a1/8481424a0888521d4cedceff0dd506b8c6b30321681a22c34fb9cd96a9c2/grpcio_tools-1.75.1-cp39-cp39-musllinux_1_2_aarch64.whl", upload-time = 2025-09-26T09:09:57Z, size = 3106320, hashes = { sha256 = "dd13f0d87605eb34f8b8868e3ad9202b90e9e58417276db79c3298538d0d60e3" } }, + { url = "https://files.pythonhosted.org/packages/05/2d/c73b3cbc65b57445bfdc010754ba404db70a08f95785a3715cd3310385d3/grpcio_tools-1.75.1-cp39-cp39-musllinux_1_2_i686.whl", upload-time = 2025-09-26T09:10:00Z, size = 3654945, hashes = { sha256 = "9555db0d2eb22850b7e9a27c0476627d483c114fcdf40d29b03aef446f5e4c43" } }, + { url = "https://files.pythonhosted.org/packages/dc/81/9f582812060833168ef667b240e81bfe32fc56db5aa40f3c47e1e4008245/grpcio_tools-1.75.1-cp39-cp39-musllinux_1_2_x86_64.whl", upload-time = 2025-09-26T09:10:04Z, size = 3322607, hashes = { sha256 = "a35800ce3ecea4aaad511bc18daccd37b1560132694f30b606f2044f1242c9a0" } }, + { url = "https://files.pythonhosted.org/packages/41/8c/a05183563416b72b7083e9f14fb6d9e3331dc86dba20c24094bc6e84069f/grpcio_tools-1.75.1-cp39-cp39-win32.whl", upload-time = 2025-09-26T09:10:06Z, size = 993298, hashes = { sha256 = "8e7f2c1a37a5c8db92c5cba4034c370598f7458b275606f7a2a114f8c25c0326" } }, + { url = "https://files.pythonhosted.org/packages/62/1a/199be72b2315e6356e1b75dfcad7a4d41cb73ae6ad31c058156c3753083a/grpcio_tools-1.75.1-cp39-cp39-win_amd64.whl", upload-time = 2025-09-26T09:10:08Z, size = 1157997, hashes = { sha256 = "0de3a82ee33d960f117ab66da51254cccd8bda9118d11ec3379f954cfbf6bc39" } }, +] + +[[packages]] 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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", upload-time = 2025-04-24T03:35:25Z, size = 101250, hashes = { sha256 = "4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", upload-time = 2025-04-24T03:35:24Z, size = 37515, hashes = { sha256 = "63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" } }] -[[package]] +[[packages]] name = "hf-xet" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/2c/70009910fcbd204bde75842b60c1e47fe72edb0e978954cb8001735885c7/hf_xet-1.1.0.tar.gz", hash = "sha256:a7c2a4c2b6eee9ce0a1a367a82b60d95ba634420ef1c250addad7aa4af419cf4", size = 263996 } +version = "1.1.10" +marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" +sdist = { url = "https://files.pythonhosted.org/packages/74/31/feeddfce1748c4a233ec1aa5b7396161c07ae1aa9b7bdbc9a72c3c7dd768/hf_xet-1.1.10.tar.gz", upload-time = 2025-09-12T20:10:27Z, size = 487910, hashes = { sha256 = "408aef343800a2102374a883f283ff29068055c111f003ff840733d3b715bb97" } } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/fd/0db331297e331f0f02005fd7ea666439bf15efd74f0dd62af02a43236a1b/hf_xet-1.1.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0322c42551e275fcb7949c083a54a81b2898e50787c9aa74284fcb8d2c58c12c", size = 5069444 }, - { url = "https://files.pythonhosted.org/packages/b9/7d/4d7ae44219d3744ad55669cb90ef3d4ed9f5f8a4729fa635a6499491cb78/hf_xet-1.1.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:667153a0304ac2debf2af95a8ff7687186f885b493f4cd16344869af270cd110", size = 4881465 }, - { url = "https://files.pythonhosted.org/packages/83/9a/d40d2a57b132d609d8a4ccc29e59ed69749021610616749cabcda2532158/hf_xet-1.1.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:995eeffb119636ea617b96c7d7bf3c3f5ea8727fa57974574e25d700b8532d48", size = 53584225 }, - { url = "https://files.pythonhosted.org/packages/2e/01/d94553f91d85746e0862f24d239da88d10f5ce252b028565744e982432f4/hf_xet-1.1.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3aee847da362393331f515c4010d0aaa1c2669acfcca1f4b28946d6949cc0086", size = 52043680 }, - { url = "https://files.pythonhosted.org/packages/29/89/1f31853bf378f0ceb3363c07fd8a12af9b904b1f8c21e65eb5c19397bc98/hf_xet-1.1.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68c5813a6074aa36e12ef5983230e3b03148cce61e0fcdd294096493795565b4", size = 53072672 }, - { url = "https://files.pythonhosted.org/packages/b5/9f/5ecb92b18a4b2135a72a95dc08bcbeda9176f46642c745ee052420d2aea8/hf_xet-1.1.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4ee9222bf9274b1c198b88a929de0b5a49349c4962d89c5b3b2f0f7f47d9761c", size = 53521053 }, - { url = "https://files.pythonhosted.org/packages/53/d6/cb32842cbf1cf5a154b41fa918a2fd86003af9bca227a2397cd7f312a8a6/hf_xet-1.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:73153eab9abf3d6973b21e94a67ccba5d595c3e12feb8c0bf50be02964e7f126", size = 4204376 }, + { url = "https://files.pythonhosted.org/packages/f7/a2/343e6d05de96908366bdc0081f2d8607d61200be2ac802769c4284cc65bd/hf_xet-1.1.10-cp37-abi3-macosx_10_12_x86_64.whl", upload-time = 2025-09-12T20:10:22Z, size = 2761466, hashes = { sha256 = "686083aca1a6669bc85c21c0563551cbcdaa5cf7876a91f3d074a030b577231d" } }, + { url = "https://files.pythonhosted.org/packages/31/f9/6215f948ac8f17566ee27af6430ea72045e0418ce757260248b483f4183b/hf_xet-1.1.10-cp37-abi3-macosx_11_0_arm64.whl", upload-time = 2025-09-12T20:10:21Z, size = 2623807, hashes = { sha256 = "71081925383b66b24eedff3013f8e6bbd41215c3338be4b94ba75fd75b21513b" } }, + { url = "https://files.pythonhosted.org/packages/15/07/86397573efefff941e100367bbda0b21496ffcdb34db7ab51912994c32a2/hf_xet-1.1.10-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-09-12T20:10:19Z, size = 3186960, hashes = { sha256 = "6b6bceb6361c80c1cc42b5a7b4e3efd90e64630bcf11224dcac50ef30a47e435" } }, + { url = "https://files.pythonhosted.org/packages/01/a7/0b2e242b918cc30e1f91980f3c4b026ff2eedaf1e2ad96933bca164b2869/hf_xet-1.1.10-cp37-abi3-manylinux_2_28_aarch64.whl", upload-time = 2025-09-12T20:10:17Z, size = 3087167, hashes = { sha256 = "eae7c1fc8a664e54753ffc235e11427ca61f4b0477d757cc4eb9ae374b69f09c" } }, + { url = "https://files.pythonhosted.org/packages/4a/25/3e32ab61cc7145b11eee9d745988e2f0f4fafda81b25980eebf97d8cff15/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_aarch64.whl", upload-time = 2025-09-12T20:10:24Z, size = 3248612, hashes = { sha256 = "0a0005fd08f002180f7a12d4e13b22be277725bc23ed0529f8add5c7a6309c06" } }, + { url = "https://files.pythonhosted.org/packages/2c/3d/ab7109e607ed321afaa690f557a9ada6d6d164ec852fd6bf9979665dc3d6/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_x86_64.whl", upload-time = 2025-09-12T20:10:25Z, size = 3353360, hashes = { sha256 = "f900481cf6e362a6c549c61ff77468bd59d6dd082f3170a36acfef2eb6a6793f" } }, + { url = "https://files.pythonhosted.org/packages/ee/0e/471f0a21db36e71a2f1752767ad77e92d8cde24e974e03d662931b1305ec/hf_xet-1.1.10-cp37-abi3-win_amd64.whl", upload-time = 2025-09-12T20:10:28Z, size = 2804691, hashes = { sha256 = "5f54b19cc347c13235ae7ee98b330c26dd65ef1df47e5316ffb1e87713ca7045" } }, ] -[[package]] +[[packages]] 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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, -] - -[[package]] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", upload-time = 2025-04-24T22:06:22Z, size = 85484, hashes = { sha256 = "6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", upload-time = 2025-04-24T22:06:20Z, size = 78784, hashes = { sha256 = "2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55" } }] + +[[packages]] +name = "httptools" +version = "0.7.1" +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", upload-time = 2025-10-10T03:55:08Z, size = 258961, hashes = { sha256 = "abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/e5/c07e0bcf4ec8db8164e9f6738c048b2e66aabf30e7506f440c4cc6953f60/httptools-0.7.1-cp310-cp310-macosx_10_9_universal2.whl", upload-time = 2025-10-10T03:54:20Z, size = 204531, hashes = { sha256 = "11d01b0ff1fe02c4c32d60af61a4d613b74fad069e47e06e9067758c01e9ac78" } }, + { url = "https://files.pythonhosted.org/packages/7e/4f/35e3a63f863a659f92ffd92bef131f3e81cf849af26e6435b49bd9f6f751/httptools-0.7.1-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-10-10T03:54:22Z, size = 109408, hashes = { sha256 = "84d86c1e5afdc479a6fdabf570be0d3eb791df0ae727e8dbc0259ed1249998d4" } }, + { url = "https://files.pythonhosted.org/packages/f5/71/b0a9193641d9e2471ac541d3b1b869538a5fb6419d52fd2669fa9c79e4b8/httptools-0.7.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-10T03:54:23Z, size = 440889, hashes = { sha256 = "c8c751014e13d88d2be5f5f14fc8b89612fcfa92a9cc480f2bc1598357a23a05" } }, + { url = "https://files.pythonhosted.org/packages/eb/d9/2e34811397b76718750fea44658cb0205b84566e895192115252e008b152/httptools-0.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-10T03:54:25Z, size = 440460, hashes = { sha256 = "654968cb6b6c77e37b832a9be3d3ecabb243bbe7a0b8f65fbc5b6b04c8fcabed" } }, + { url = "https://files.pythonhosted.org/packages/01/3f/a04626ebeacc489866bb4d82362c0657b2262bef381d68310134be7f40bb/httptools-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-10-10T03:54:26Z, size = 425267, hashes = { sha256 = "b580968316348b474b020edf3988eecd5d6eec4634ee6561e72ae3a2a0e00a8a" } }, + { url = "https://files.pythonhosted.org/packages/a5/99/adcd4f66614db627b587627c8ad6f4c55f18881549bab10ecf180562e7b9/httptools-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-10-10T03:54:28Z, size = 424429, hashes = { sha256 = "d496e2f5245319da9d764296e86c5bb6fcf0cf7a8806d3d000717a889c8c0b7b" } }, + { url = "https://files.pythonhosted.org/packages/d5/72/ec8fc904a8fd30ba022dfa85f3bbc64c3c7cd75b669e24242c0658e22f3c/httptools-0.7.1-cp310-cp310-win_amd64.whl", upload-time = 2025-10-10T03:54:29Z, size = 86173, hashes = { sha256 = "cbf8317bfccf0fed3b5680c559d3459cccf1abe9039bfa159e62e391c7270568" } }, + { url = "https://files.pythonhosted.org/packages/9c/08/17e07e8d89ab8f343c134616d72eebfe03798835058e2ab579dcc8353c06/httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl", upload-time = 2025-10-10T03:54:31Z, size = 206521, hashes = { sha256 = "474d3b7ab469fefcca3697a10d11a32ee2b9573250206ba1e50d5980910da657" } }, + { url = "https://files.pythonhosted.org/packages/aa/06/c9c1b41ff52f16aee526fd10fbda99fa4787938aa776858ddc4a1ea825ec/httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-10-10T03:54:31Z, size = 110375, hashes = { sha256 = "a3c3b7366bb6c7b96bd72d0dbe7f7d5eead261361f013be5f6d9590465ea1c70" } }, + { url = "https://files.pythonhosted.org/packages/cc/cc/10935db22fda0ee34c76f047590ca0a8bd9de531406a3ccb10a90e12ea21/httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-10T03:54:33Z, size = 456621, hashes = { sha256 = "379b479408b8747f47f3b253326183d7c009a3936518cdb70db58cffd369d9df" } }, + { url = "https://files.pythonhosted.org/packages/0e/84/875382b10d271b0c11aa5d414b44f92f8dd53e9b658aec338a79164fa548/httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-10T03:54:34Z, size = 454954, hashes = { sha256 = "cad6b591a682dcc6cf1397c3900527f9affef1e55a06c4547264796bbd17cf5e" } }, + { url = "https://files.pythonhosted.org/packages/30/e1/44f89b280f7e46c0b1b2ccee5737d46b3bb13136383958f20b580a821ca0/httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-10-10T03:54:35Z, size = 440175, hashes = { sha256 = "eb844698d11433d2139bbeeb56499102143beb582bd6c194e3ba69c22f25c274" } }, + { url = "https://files.pythonhosted.org/packages/6f/7e/b9287763159e700e335028bc1824359dc736fa9b829dacedace91a39b37e/httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-10-10T03:54:37Z, size = 440310, hashes = { sha256 = "f65744d7a8bdb4bda5e1fa23e4ba16832860606fcc09d674d56e425e991539ec" } }, + { url = "https://files.pythonhosted.org/packages/b3/07/5b614f592868e07f5c94b1f301b5e14a21df4e8076215a3bccb830a687d8/httptools-0.7.1-cp311-cp311-win_amd64.whl", upload-time = 2025-10-10T03:54:38Z, size = 86875, hashes = { sha256 = "135fbe974b3718eada677229312e97f3b31f8a9c8ffa3ae6f565bf808d5b6bcb" } }, + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", upload-time = 2025-10-10T03:54:39Z, size = 206280, hashes = { sha256 = "38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5" } }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-10-10T03:54:40Z, size = 110004, hashes = { sha256 = "f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5" } }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-10T03:54:41Z, size = 517655, hashes = { sha256 = "2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03" } }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-10T03:54:42Z, size = 511440, hashes = { sha256 = "7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2" } }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-10-10T03:54:43Z, size = 495186, hashes = { sha256 = "f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362" } }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-10-10T03:54:45Z, size = 499192, hashes = { sha256 = "e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c" } }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", upload-time = 2025-10-10T03:54:45Z, size = 86694, hashes = { sha256 = "3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321" } }, + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", upload-time = 2025-10-10T03:54:47Z, size = 202889, hashes = { sha256 = "6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3" } }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-10-10T03:54:48Z, size = 108180, hashes = { sha256 = "601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca" } }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-10T03:54:48Z, size = 478596, hashes = { sha256 = "04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c" } }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-10T03:54:49Z, size = 473268, hashes = { sha256 = "69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66" } }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-10-10T03:54:51Z, size = 455517, hashes = { sha256 = "44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346" } }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-10-10T03:54:52Z, size = 458337, hashes = { sha256 = "465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650" } }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", upload-time = 2025-10-10T03:54:53Z, size = 85743, hashes = { sha256 = "322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6" } }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", upload-time = 2025-10-10T03:54:54Z, size = 203619, hashes = { sha256 = "c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270" } }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-10-10T03:54:55Z, size = 108714, hashes = { sha256 = "7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3" } }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-10T03:54:56Z, size = 472909, hashes = { sha256 = "0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1" } }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-10T03:54:57Z, size = 470831, hashes = { sha256 = "df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b" } }, + { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-10-10T03:54:58Z, size = 452631, hashes = { sha256 = "f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60" } }, + { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-10-10T03:54:59Z, size = 452910, hashes = { sha256 = "7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca" } }, + { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", upload-time = 2025-10-10T03:55:00Z, size = 88205, hashes = { sha256 = "cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96" } }, + { url = "https://files.pythonhosted.org/packages/90/de/b1fe0e8890f0292c266117d4cd268186758a9c34e576fbd573fdf3beacff/httptools-0.7.1-cp39-cp39-macosx_10_9_universal2.whl", upload-time = 2025-10-10T03:55:01Z, size = 206454, hashes = { sha256 = "ac50afa68945df63ec7a2707c506bd02239272288add34539a2ef527254626a4" } }, + { url = "https://files.pythonhosted.org/packages/57/a7/a675c90b49e550c7635ce209c01bc61daa5b08aef17da27ef4e0e78fcf3f/httptools-0.7.1-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2025-10-10T03:55:02Z, size = 110260, hashes = { sha256 = "de987bb4e7ac95b99b805b99e0aae0ad51ae61df4263459d36e07cf4052d8b3a" } }, + { url = "https://files.pythonhosted.org/packages/03/44/fb5ef8136e6e97f7b020e97e40c03a999f97e68574d4998fa52b0a62b01b/httptools-0.7.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", upload-time = 2025-10-10T03:55:03Z, size = 441524, hashes = { sha256 = "d169162803a24425eb5e4d51d79cbf429fd7a491b9e570a55f495ea55b26f0bf" } }, + { url = "https://files.pythonhosted.org/packages/b4/62/8496a5425341867796d7e2419695f74a74607054e227bbaeabec8323e87f/httptools-0.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-10T03:55:04Z, size = 440877, hashes = { sha256 = "49794f9250188a57fa73c706b46cb21a313edb00d337ca4ce1a011fe3c760b28" } }, + { url = "https://files.pythonhosted.org/packages/e8/f1/26c2e5214106bf6ed04d03e518ff28ca0c6b5390c5da7b12bbf94b40ae43/httptools-0.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", upload-time = 2025-10-10T03:55:05Z, size = 425775, hashes = { sha256 = "aeefa0648362bb97a7d6b5ff770bfb774930a327d7f65f8208394856862de517" } }, + { url = "https://files.pythonhosted.org/packages/3a/34/7500a19257139725281f7939a7d1aa3701cf1ac4601a1690f9ab6f510e15/httptools-0.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", upload-time = 2025-10-10T03:55:06Z, size = 425001, hashes = { sha256 = "0d92b10dbf0b3da4823cde6a96d18e6ae358a9daa741c71448975f6a2c339cad" } }, + { url = "https://files.pythonhosted.org/packages/71/04/31a7949d645ebf33a67f56a0024109444a52a271735e0647a210264f3e61/httptools-0.7.1-cp39-cp39-win_amd64.whl", upload-time = 2025-10-10T03:55:07Z, size = 86818, hashes = { sha256 = "5ddbd045cfcb073db2449563dd479057f2c2b681ebc232380e63ef15edc9c023" } }, +] + +[[packages]] 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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", upload-time = 2024-12-06T15:37:23Z, size = 141406, hashes = { sha256 = "75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", upload-time = 2024-12-06T15:37:21Z, size = 73517, hashes = { sha256 = "d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" } }] -[[package]] +[[packages]] name = "huggingface-hub" -version = "0.31.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/25/eb/9268c1205d19388659d5dc664f012177b752c0eef194a9159acc7227780f/huggingface_hub-0.31.1.tar.gz", hash = "sha256:492bb5f545337aa9e2f59b75ef4c5f535a371e8958a6ce90af056387e67f1180", size = 403036 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/bf/6002da17ec1c7a47bedeb216812929665927c70b6e7500b3c7bf36f01bdd/huggingface_hub-0.31.1-py3-none-any.whl", hash = "sha256:43f73124819b48b42d140cbc0d7a2e6bd15b2853b1b9d728d4d55ad1750cac5b", size = 484265 }, -] +version = "0.35.3" +sdist = { url = "https://files.pythonhosted.org/packages/10/7e/a0a97de7c73671863ca6b3f61fa12518caf35db37825e43d63a70956738c/huggingface_hub-0.35.3.tar.gz", upload-time = 2025-09-29T14:29:58Z, size = 461798, hashes = { sha256 = "350932eaa5cc6a4747efae85126ee220e4ef1b54e29d31c3b45c5612ddf0b32a" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/31/a0/651f93d154cb72323358bf2bbae3e642bdb5d2f1bfc874d096f7cb159fa0/huggingface_hub-0.35.3-py3-none-any.whl", upload-time = 2025-09-29T14:29:55Z, size = 564262, hashes = { sha256 = "0e3a01829c19d86d03793e4577816fe3bdfc1602ac62c7fb220d593d351224ba" } }] -[[package]] +[[packages]] name = "idna" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, -] +version = "3.11" +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", upload-time = 2025-10-12T14:55:20Z, size = 194582, hashes = { sha256 = "795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", upload-time = 2025-10-12T14:55:18Z, size = 71008, hashes = { sha256 = "771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" } }] -[[package]] +[[packages]] name = "iniconfig" version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", upload-time = 2025-03-19T20:09:59Z, size = 4793, hashes = { sha256 = "3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", upload-time = 2025-03-19T20:10:01Z, size = 6050, hashes = { sha256 = "9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760" } }] -[[package]] +[[packages]] 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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", upload-time = 2025-03-05T20:05:02Z, size = 245115, hashes = { sha256 = "0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", upload-time = 2025-03-05T20:05:00Z, size = 134899, hashes = { sha256 = "85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" } }] -[[package]] +[[packages]] name = "jiter" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/c2/e4562507f52f0af7036da125bb699602ead37a2332af0788f8e0a3417f36/jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893", size = 162604 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/1b/4cd165c362e8f2f520fdb43245e2b414f42a255921248b4f8b9c8d871ff1/jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7", size = 308197 }, - { url = "https://files.pythonhosted.org/packages/13/aa/7a890dfe29c84c9a82064a9fe36079c7c0309c91b70c380dc138f9bea44a/jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b", size = 318160 }, - { url = "https://files.pythonhosted.org/packages/6a/38/5888b43fc01102f733f085673c4f0be5a298f69808ec63de55051754e390/jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69", size = 341259 }, - { url = "https://files.pythonhosted.org/packages/3d/5e/bbdbb63305bcc01006de683b6228cd061458b9b7bb9b8d9bc348a58e5dc2/jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103", size = 363730 }, - { url = "https://files.pythonhosted.org/packages/75/85/53a3edc616992fe4af6814c25f91ee3b1e22f7678e979b6ea82d3bc0667e/jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635", size = 405126 }, - { url = "https://files.pythonhosted.org/packages/ae/b3/1ee26b12b2693bd3f0b71d3188e4e5d817b12e3c630a09e099e0a89e28fa/jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4", size = 393668 }, - { url = "https://files.pythonhosted.org/packages/11/87/e084ce261950c1861773ab534d49127d1517b629478304d328493f980791/jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d", size = 352350 }, - { url = "https://files.pythonhosted.org/packages/f0/06/7dca84b04987e9df563610aa0bc154ea176e50358af532ab40ffb87434df/jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3", size = 384204 }, - { url = "https://files.pythonhosted.org/packages/16/2f/82e1c6020db72f397dd070eec0c85ebc4df7c88967bc86d3ce9864148f28/jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5", size = 520322 }, - { url = "https://files.pythonhosted.org/packages/36/fd/4f0cd3abe83ce208991ca61e7e5df915aa35b67f1c0633eb7cf2f2e88ec7/jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d", size = 512184 }, - { url = "https://files.pythonhosted.org/packages/a0/3c/8a56f6d547731a0b4410a2d9d16bf39c861046f91f57c98f7cab3d2aa9ce/jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53", size = 206504 }, - { url = "https://files.pythonhosted.org/packages/f4/1c/0c996fd90639acda75ed7fa698ee5fd7d80243057185dc2f63d4c1c9f6b9/jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7", size = 204943 }, - { url = "https://files.pythonhosted.org/packages/78/0f/77a63ca7aa5fed9a1b9135af57e190d905bcd3702b36aca46a01090d39ad/jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001", size = 317281 }, - { url = "https://files.pythonhosted.org/packages/f9/39/a3a1571712c2bf6ec4c657f0d66da114a63a2e32b7e4eb8e0b83295ee034/jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a", size = 350273 }, - { url = "https://files.pythonhosted.org/packages/ee/47/3729f00f35a696e68da15d64eb9283c330e776f3b5789bac7f2c0c4df209/jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf", size = 206867 }, -] - -[[package]] +version = "0.11.0" +sdist = { url = "https://files.pythonhosted.org/packages/9d/c0/a3bb4cc13aced219dd18191ea66e874266bd8aa7b96744e495e1c733aa2d/jiter-0.11.0.tar.gz", upload-time = 2025-09-15T09:20:38Z, size = 167094, hashes = { sha256 = "1d9637eaf8c1d6a63d6562f2a6e5ab3af946c66037eb1b894e8fad75422266e4" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/21/7dd1235a19e26979be6098e87e4cced2e061752f3a40a17bbce6dea7fae1/jiter-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", upload-time = 2025-09-15T09:18:48Z, size = 309875, hashes = { sha256 = "3893ce831e1c0094a83eeaf56c635a167d6fa8cc14393cc14298fd6fdc2a2449" } }, + { url = "https://files.pythonhosted.org/packages/71/f9/462b54708aa85b135733ccba70529dd68a18511bf367a87c5fd28676c841/jiter-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-09-15T09:18:51Z, size = 316505, hashes = { sha256 = "25c625b9b61b5a8725267fdf867ef2e51b429687f6a4eef211f4612e95607179" } }, + { url = "https://files.pythonhosted.org/packages/bd/40/14e2eeaac6a47bff27d213834795472355fd39769272eb53cb7aa83d5aa8/jiter-0.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-09-15T09:18:52Z, size = 337613, hashes = { sha256 = "dd4ca85fb6a62cf72e1c7f5e34ddef1b660ce4ed0886ec94a1ef9777d35eaa1f" } }, + { url = "https://files.pythonhosted.org/packages/d3/ed/a5f1f8419c92b150a7c7fb5ccba1fb1e192887ad713d780e70874f0ce996/jiter-0.11.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-09-15T09:18:54Z, size = 361438, hashes = { sha256 = "572208127034725e79c28437b82414028c3562335f2b4f451d98136d0fc5f9cd" } }, + { url = "https://files.pythonhosted.org/packages/dd/f5/70682c023dfcdd463a53faf5d30205a7d99c51d70d3e303c932d0936e5a2/jiter-0.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-09-15T09:18:56Z, size = 486180, hashes = { sha256 = "494ba627c7f550ad3dabb21862864b8f2216098dc18ff62f37b37796f2f7c325" } }, + { url = "https://files.pythonhosted.org/packages/7c/39/020d08cbab4eab48142ad88b837c41eb08a15c0767fdb7c0d3265128a44b/jiter-0.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-09-15T09:18:57Z, size = 376681, hashes = { sha256 = "b8da18a99f58bca3ecc2d2bba99cac000a924e115b6c4f0a2b98f752b6fbf39a" } }, + { url = "https://files.pythonhosted.org/packages/52/10/b86733f6e594cf51dd142f37c602d8df87c554c5844958deaab0de30eb5d/jiter-0.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-09-15T09:18:59Z, size = 348685, hashes = { sha256 = "e4ffd3b0fff3fabbb02cc09910c08144db6bb5697a98d227a074401e01ee63dd" } }, + { url = "https://files.pythonhosted.org/packages/fb/ee/8861665e83a9e703aa5f65fddddb6225428e163e6b0baa95a7f9a8fb9aae/jiter-0.11.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-09-15T09:19:00Z, size = 385573, hashes = { sha256 = "8fe6530aa738a4f7d4e4702aa8f9581425d04036a5f9e25af65ebe1f708f23be" } }, + { url = "https://files.pythonhosted.org/packages/25/74/05afec03600951f128293813b5a208c9ba1bf587c57a344c05a42a69e1b1/jiter-0.11.0-cp310-cp310-musllinux_1_1_aarch64.whl", upload-time = 2025-09-15T09:19:02Z, size = 516669, hashes = { sha256 = "e35d66681c133a03d7e974e7eedae89720fe8ca3bd09f01a4909b86a8adf31f5" } }, + { url = "https://files.pythonhosted.org/packages/93/d1/2e5bfe147cfbc2a5eef7f73eb75dc5c6669da4fa10fc7937181d93af9495/jiter-0.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", upload-time = 2025-09-15T09:19:04Z, size = 508767, hashes = { sha256 = "c59459beca2fbc9718b6f1acb7bfb59ebc3eb4294fa4d40e9cb679dafdcc6c60" } }, + { url = "https://files.pythonhosted.org/packages/87/50/597f71307e10426b5c082fd05d38c615ddbdd08c3348d8502963307f0652/jiter-0.11.0-cp310-cp310-win32.whl", upload-time = 2025-09-15T09:19:05Z, size = 205476, hashes = { sha256 = "b7b0178417b0dcfc5f259edbc6db2b1f5896093ed9035ee7bab0f2be8854726d" } }, + { url = "https://files.pythonhosted.org/packages/c7/86/1e5214b3272e311754da26e63edec93a183811d4fc2e0118addec365df8b/jiter-0.11.0-cp310-cp310-win_amd64.whl", upload-time = 2025-09-15T09:19:06Z, size = 204708, hashes = { sha256 = "11df2bf99fb4754abddd7f5d940a48e51f9d11624d6313ca4314145fcad347f0" } }, + { url = "https://files.pythonhosted.org/packages/38/55/a69fefeef09c2eaabae44b935a1aa81517e49639c0a0c25d861cb18cd7ac/jiter-0.11.0-cp311-cp311-macosx_10_12_x86_64.whl", upload-time = 2025-09-15T09:19:08Z, size = 309503, hashes = { sha256 = "cb5d9db02979c3f49071fce51a48f4b4e4cf574175fb2b11c7a535fa4867b222" } }, + { url = "https://files.pythonhosted.org/packages/bd/d5/a6aba9e6551f32f9c127184f398208e4eddb96c59ac065c8a92056089d28/jiter-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-09-15T09:19:09Z, size = 317688, hashes = { sha256 = "1dc6a123f3471c4730db7ca8ba75f1bb3dcb6faeb8d46dd781083e7dee88b32d" } }, + { url = "https://files.pythonhosted.org/packages/bb/f3/5e86f57c1883971cdc8535d0429c2787bf734840a231da30a3be12850562/jiter-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-09-15T09:19:11Z, size = 337418, hashes = { sha256 = "09858f8d230f031c7b8e557429102bf050eea29c77ad9c34c8fe253c5329acb7" } }, + { url = "https://files.pythonhosted.org/packages/5e/4f/a71d8a24c2a70664970574a8e0b766663f5ef788f7fe1cc20ee0c016d488/jiter-0.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-09-15T09:19:13Z, size = 361423, hashes = { sha256 = "dbe2196c4a0ce760925a74ab4456bf644748ab0979762139626ad138f6dac72d" } }, + { url = "https://files.pythonhosted.org/packages/8f/e5/b09076f4e7fd9471b91e16f9f3dc7330b161b738f3b39b2c37054a36e26a/jiter-0.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-09-15T09:19:14Z, size = 486367, hashes = { sha256 = "5beb56d22b63647bafd0b74979216fdee80c580c0c63410be8c11053860ffd09" } }, + { url = "https://files.pythonhosted.org/packages/fb/f1/98cb3a36f5e62f80cd860f0179f948d9eab5a316d55d3e1bab98d9767af5/jiter-0.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-09-15T09:19:15Z, size = 376335, hashes = { sha256 = "97025d09ef549795d8dc720a824312cee3253c890ac73c621721ddfc75066789" } }, + { url = "https://files.pythonhosted.org/packages/9f/d8/ec74886497ea393c29dbd7651ddecc1899e86404a6b1f84a3ddab0ab59fd/jiter-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-09-15T09:19:17Z, size = 348981, hashes = { sha256 = "d50880a6da65d8c23a2cf53c412847d9757e74cc9a3b95c5704a1d1a24667347" } }, + { url = "https://files.pythonhosted.org/packages/24/93/d22ad7fa3b86ade66c86153ceea73094fc2af8b20c59cb7fceab9fea4704/jiter-0.11.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-09-15T09:19:19Z, size = 385797, hashes = { sha256 = "452d80a1c86c095a242007bd9fc5d21b8a8442307193378f891cb8727e469648" } }, + { url = "https://files.pythonhosted.org/packages/c8/bd/e25ff4a4df226e9b885f7cb01ee4b9dc74e3000e612d6f723860d71a1f34/jiter-0.11.0-cp311-cp311-musllinux_1_1_aarch64.whl", upload-time = 2025-09-15T09:19:20Z, size = 516597, hashes = { sha256 = "e84e58198d4894668eec2da660ffff60e0f3e60afa790ecc50cb12b0e02ca1d4" } }, + { url = "https://files.pythonhosted.org/packages/be/fb/beda613db7d93ffa2fdd2683f90f2f5dce8daf4bc2d0d2829e7de35308c6/jiter-0.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", upload-time = 2025-09-15T09:19:22Z, size = 508853, hashes = { sha256 = "df64edcfc5dd5279a791eea52aa113d432c933119a025b0b5739f90d2e4e75f1" } }, + { url = "https://files.pythonhosted.org/packages/20/64/c5b0d93490634e41e38e2a15de5d54fdbd2c9f64a19abb0f95305b63373c/jiter-0.11.0-cp311-cp311-win32.whl", upload-time = 2025-09-15T09:19:23Z, size = 205140, hashes = { sha256 = "144fc21337d21b1d048f7f44bf70881e1586401d405ed3a98c95a114a9994982" } }, + { url = "https://files.pythonhosted.org/packages/a1/e6/c347c0e6f5796e97d4356b7e5ff0ce336498b7f4ef848fae621a56f1ccf3/jiter-0.11.0-cp311-cp311-win_amd64.whl", upload-time = 2025-09-15T09:19:24Z, size = 204311, hashes = { sha256 = "b0f32e644d241293b892b1a6dd8f0b9cc029bfd94c97376b2681c36548aabab7" } }, + { url = "https://files.pythonhosted.org/packages/ba/b5/3009b112b8f673e568ef79af9863d8309a15f0a8cdcc06ed6092051f377e/jiter-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", upload-time = 2025-09-15T09:19:25Z, size = 305510, hashes = { sha256 = "2fb7b377688cc3850bbe5c192a6bd493562a0bc50cbc8b047316428fbae00ada" } }, + { url = "https://files.pythonhosted.org/packages/fe/82/15514244e03b9e71e086bbe2a6de3e4616b48f07d5f834200c873956fb8c/jiter-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-09-15T09:19:27Z, size = 316521, hashes = { sha256 = "a1b7cbe3f25bd0d8abb468ba4302a5d45617ee61b2a7a638f63fee1dc086be99" } }, + { url = "https://files.pythonhosted.org/packages/92/94/7a2e905f40ad2d6d660e00b68d818f9e29fb87ffe82774f06191e93cbe4a/jiter-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-09-15T09:19:28Z, size = 338214, hashes = { sha256 = "c0a7f0ec81d5b7588c5cade1eb1925b91436ae6726dc2df2348524aeabad5de6" } }, + { url = "https://files.pythonhosted.org/packages/a8/9c/5791ed5bdc76f12110158d3316a7a3ec0b1413d018b41c5ed399549d3ad5/jiter-0.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-09-15T09:19:30Z, size = 361280, hashes = { sha256 = "07630bb46ea2a6b9c6ed986c6e17e35b26148cce2c535454b26ee3f0e8dcaba1" } }, + { url = "https://files.pythonhosted.org/packages/d4/7f/b7d82d77ff0d2cb06424141000176b53a9e6b16a1125525bb51ea4990c2e/jiter-0.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-09-15T09:19:31Z, size = 487895, hashes = { sha256 = "7764f27d28cd4a9cbc61704dfcd80c903ce3aad106a37902d3270cd6673d17f4" } }, + { url = "https://files.pythonhosted.org/packages/42/44/10a1475d46f1fc1fd5cc2e82c58e7bca0ce5852208e0fa5df2f949353321/jiter-0.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-09-15T09:19:32Z, size = 378421, hashes = { sha256 = "1d4a6c4a737d486f77f842aeb22807edecb4a9417e6700c7b981e16d34ba7c72" } }, + { url = "https://files.pythonhosted.org/packages/9a/5f/0dc34563d8164d31d07bc09d141d3da08157a68dcd1f9b886fa4e917805b/jiter-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-09-15T09:19:34Z, size = 347932, hashes = { sha256 = "cf408d2a0abd919b60de8c2e7bc5eeab72d4dafd18784152acc7c9adc3291591" } }, + { url = "https://files.pythonhosted.org/packages/f7/de/b68f32a4fcb7b4a682b37c73a0e5dae32180140cd1caf11aef6ad40ddbf2/jiter-0.11.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-09-15T09:19:35Z, size = 386959, hashes = { sha256 = "cdef53eda7d18e799625023e1e250dbc18fbc275153039b873ec74d7e8883e09" } }, + { url = "https://files.pythonhosted.org/packages/76/0a/c08c92e713b6e28972a846a81ce374883dac2f78ec6f39a0dad9f2339c3a/jiter-0.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", upload-time = 2025-09-15T09:19:37Z, size = 517187, hashes = { sha256 = "53933a38ef7b551dd9c7f1064f9d7bb235bb3168d0fa5f14f0798d1b7ea0d9c5" } }, + { url = "https://files.pythonhosted.org/packages/89/b5/4a283bec43b15aad54fcae18d951f06a2ec3f78db5708d3b59a48e9c3fbd/jiter-0.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", upload-time = 2025-09-15T09:19:38Z, size = 509461, hashes = { sha256 = "11840d2324c9ab5162fc1abba23bc922124fedcff0d7b7f85fffa291e2f69206" } }, + { url = "https://files.pythonhosted.org/packages/34/a5/f8bad793010534ea73c985caaeef8cc22dfb1fedb15220ecdf15c623c07a/jiter-0.11.0-cp312-cp312-win32.whl", upload-time = 2025-09-15T09:19:40Z, size = 206664, hashes = { sha256 = "4f01a744d24a5f2bb4a11657a1b27b61dc038ae2e674621a74020406e08f749b" } }, + { url = "https://files.pythonhosted.org/packages/ed/42/5823ec2b1469395a160b4bf5f14326b4a098f3b6898fbd327366789fa5d3/jiter-0.11.0-cp312-cp312-win_amd64.whl", upload-time = 2025-09-15T09:19:41Z, size = 203520, hashes = { sha256 = "29fff31190ab3a26de026da2f187814f4b9c6695361e20a9ac2123e4d4378a4c" } }, + { url = "https://files.pythonhosted.org/packages/97/c4/d530e514d0f4f29b2b68145e7b389cbc7cac7f9c8c23df43b04d3d10fa3e/jiter-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", upload-time = 2025-09-15T09:19:43Z, size = 305021, hashes = { sha256 = "4441a91b80a80249f9a6452c14b2c24708f139f64de959943dfeaa6cb915e8eb" } }, + { url = "https://files.pythonhosted.org/packages/7a/77/796a19c567c5734cbfc736a6f987affc0d5f240af8e12063c0fb93990ffa/jiter-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-09-15T09:19:44Z, size = 314384, hashes = { sha256 = "ff85fc6d2a431251ad82dbd1ea953affb5a60376b62e7d6809c5cd058bb39471" } }, + { url = "https://files.pythonhosted.org/packages/14/9c/824334de0b037b91b6f3fa9fe5a191c83977c7ec4abe17795d3cb6d174cf/jiter-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-09-15T09:19:46Z, size = 337389, hashes = { sha256 = "c5e86126d64706fd28dfc46f910d496923c6f95b395138c02d0e252947f452bd" } }, + { url = "https://files.pythonhosted.org/packages/a2/95/ed4feab69e6cf9b2176ea29d4ef9d01a01db210a3a2c8a31a44ecdc68c38/jiter-0.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-09-15T09:19:47Z, size = 360519, hashes = { sha256 = "4ad8bd82165961867a10f52010590ce0b7a8c53da5ddd8bbb62fef68c181b921" } }, + { url = "https://files.pythonhosted.org/packages/b5/0c/2ad00f38d3e583caba3909d95b7da1c3a7cd82c0aa81ff4317a8016fb581/jiter-0.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-09-15T09:19:49Z, size = 487198, hashes = { sha256 = "b42c2cd74273455ce439fd9528db0c6e84b5623cb74572305bdd9f2f2961d3df" } }, + { url = "https://files.pythonhosted.org/packages/ea/8b/919b64cf3499b79bdfba6036da7b0cac5d62d5c75a28fb45bad7819e22f0/jiter-0.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-09-15T09:19:50Z, size = 377835, hashes = { sha256 = "f0062dab98172dd0599fcdbf90214d0dcde070b1ff38a00cc1b90e111f071982" } }, + { url = "https://files.pythonhosted.org/packages/29/7f/8ebe15b6e0a8026b0d286c083b553779b4dd63db35b43a3f171b544de91d/jiter-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-09-15T09:19:51Z, size = 347655, hashes = { sha256 = "bb948402821bc76d1f6ef0f9e19b816f9b09f8577844ba7140f0b6afe994bc64" } }, + { url = "https://files.pythonhosted.org/packages/8e/64/332127cef7e94ac75719dda07b9a472af6158ba819088d87f17f3226a769/jiter-0.11.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-09-15T09:19:53Z, size = 386135, hashes = { sha256 = "25a5b1110cca7329fd0daf5060faa1234be5c11e988948e4f1a1923b6a457fe1" } }, + { url = "https://files.pythonhosted.org/packages/20/c8/557b63527442f84c14774159948262a9d4fabb0d61166f11568f22fc60d2/jiter-0.11.0-cp313-cp313-musllinux_1_1_aarch64.whl", upload-time = 2025-09-15T09:19:54Z, size = 516063, hashes = { sha256 = "bf11807e802a214daf6c485037778843fadd3e2ec29377ae17e0706ec1a25758" } }, + { url = "https://files.pythonhosted.org/packages/86/13/4164c819df4a43cdc8047f9a42880f0ceef5afeb22e8b9675c0528ebdccd/jiter-0.11.0-cp313-cp313-musllinux_1_1_x86_64.whl", upload-time = 2025-09-15T09:19:55Z, size = 508139, hashes = { sha256 = "dbb57da40631c267861dd0090461222060960012d70fd6e4c799b0f62d0ba166" } }, + { url = "https://files.pythonhosted.org/packages/fa/70/6e06929b401b331d41ddb4afb9f91cd1168218e3371972f0afa51c9f3c31/jiter-0.11.0-cp313-cp313-win32.whl", upload-time = 2025-09-15T09:19:57Z, size = 206369, hashes = { sha256 = "8e36924dad32c48d3c5e188d169e71dc6e84d6cb8dedefea089de5739d1d2f80" } }, + { url = "https://files.pythonhosted.org/packages/f4/0d/8185b8e15de6dce24f6afae63380e16377dd75686d56007baa4f29723ea1/jiter-0.11.0-cp313-cp313-win_amd64.whl", upload-time = 2025-09-15T09:19:58Z, size = 202538, hashes = { sha256 = "452d13e4fd59698408087235259cebe67d9d49173b4dacb3e8d35ce4acf385d6" } }, + { url = "https://files.pythonhosted.org/packages/13/3a/d61707803260d59520721fa326babfae25e9573a88d8b7b9cb54c5423a59/jiter-0.11.0-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-09-15T09:19:59Z, size = 313737, hashes = { sha256 = "089f9df9f69532d1339e83142438668f52c97cd22ee2d1195551c2b1a9e6cf33" } }, + { url = "https://files.pythonhosted.org/packages/cd/cc/c9f0eec5d00f2a1da89f6bdfac12b8afdf8d5ad974184863c75060026457/jiter-0.11.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-09-15T09:20:01Z, size = 346183, hashes = { sha256 = "29ed1fe69a8c69bf0f2a962d8d706c7b89b50f1332cd6b9fbda014f60bd03a03" } }, + { url = "https://files.pythonhosted.org/packages/a6/87/fc632776344e7aabbab05a95a0075476f418c5d29ab0f2eec672b7a1f0ac/jiter-0.11.0-cp313-cp313t-win_amd64.whl", upload-time = 2025-09-15T09:20:03Z, size = 204225, hashes = { sha256 = "a4d71d7ea6ea8786291423fe209acf6f8d398a0759d03e7f24094acb8ab686ba" } }, + { url = "https://files.pythonhosted.org/packages/ee/3b/e7f45be7d3969bdf2e3cd4b816a7a1d272507cd0edd2d6dc4b07514f2d9a/jiter-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", upload-time = 2025-09-15T09:20:04Z, size = 304414, hashes = { sha256 = "9a6dff27eca70930bdbe4cbb7c1a4ba8526e13b63dc808c0670083d2d51a4a72" } }, + { url = "https://files.pythonhosted.org/packages/06/32/13e8e0d152631fcc1907ceb4943711471be70496d14888ec6e92034e2caf/jiter-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-09-15T09:20:05Z, size = 314223, hashes = { sha256 = "b1ae2a7593a62132c7d4c2abbee80bbbb94fdc6d157e2c6cc966250c564ef774" } }, + { url = "https://files.pythonhosted.org/packages/0c/7e/abedd5b5a20ca083f778d96bba0d2366567fcecb0e6e34ff42640d5d7a18/jiter-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-09-15T09:20:06Z, size = 337306, hashes = { sha256 = "7b13a431dba4b059e9e43019d3022346d009baf5066c24dcdea321a303cde9f0" } }, + { url = "https://files.pythonhosted.org/packages/ac/e2/30d59bdc1204c86aa975ec72c48c482fee6633120ee9c3ab755e4dfefea8/jiter-0.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-09-15T09:20:08Z, size = 360565, hashes = { sha256 = "af62e84ca3889604ebb645df3b0a3f3bcf6b92babbff642bd214616f57abb93a" } }, + { url = "https://files.pythonhosted.org/packages/fe/88/567288e0d2ed9fa8f7a3b425fdaf2cb82b998633c24fe0d98f5417321aa8/jiter-0.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-09-15T09:20:09Z, size = 486465, hashes = { sha256 = "c6f3b32bb723246e6b351aecace52aba78adb8eeb4b2391630322dc30ff6c773" } }, + { url = "https://files.pythonhosted.org/packages/18/6e/7b72d09273214cadd15970e91dd5ed9634bee605176107db21e1e4205eb1/jiter-0.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-09-15T09:20:10Z, size = 377581, hashes = { sha256 = "adcab442f4a099a358a7f562eaa54ed6456fb866e922c6545a717be51dbed7d7" } }, + { url = "https://files.pythonhosted.org/packages/58/52/4db456319f9d14deed325f70102577492e9d7e87cf7097bda9769a1fcacb/jiter-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-09-15T09:20:12Z, size = 347102, hashes = { sha256 = "c9967c2ab338ee2b2c0102fd379ec2693c496abf71ffd47e4d791d1f593b68e2" } }, + { url = "https://files.pythonhosted.org/packages/ce/b4/433d5703c38b26083aec7a733eb5be96f9c6085d0e270a87ca6482cbf049/jiter-0.11.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-09-15T09:20:13Z, size = 386477, hashes = { sha256 = "e7d0bed3b187af8b47a981d9742ddfc1d9b252a7235471ad6078e7e4e5fe75c2" } }, + { url = "https://files.pythonhosted.org/packages/c8/7a/a60bfd9c55b55b07c5c441c5085f06420b6d493ce9db28d069cc5b45d9f3/jiter-0.11.0-cp314-cp314-musllinux_1_1_aarch64.whl", upload-time = 2025-09-15T09:20:14Z, size = 516004, hashes = { sha256 = "f6fe0283e903ebc55f1a6cc569b8c1f3bf4abd026fed85e3ff8598a9e6f982f0" } }, + { url = "https://files.pythonhosted.org/packages/2e/46/f8363e5ecc179b4ed0ca6cb0a6d3bfc266078578c71ff30642ea2ce2f203/jiter-0.11.0-cp314-cp314-musllinux_1_1_x86_64.whl", upload-time = 2025-09-15T09:20:16Z, size = 507855, hashes = { sha256 = "4ee5821e3d66606b29ae5b497230b304f1376f38137d69e35f8d2bd5f310ff73" } }, + { url = "https://files.pythonhosted.org/packages/90/33/396083357d51d7ff0f9805852c288af47480d30dd31d8abc74909b020761/jiter-0.11.0-cp314-cp314-win32.whl", upload-time = 2025-09-15T09:20:17Z, size = 205802, hashes = { sha256 = "c2d13ba7567ca8799f17c76ed56b1d49be30df996eb7fa33e46b62800562a5e2" } }, + { url = "https://files.pythonhosted.org/packages/e7/ab/eb06ca556b2551d41de7d03bf2ee24285fa3d0c58c5f8d95c64c9c3281b1/jiter-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-09-15T09:20:18Z, size = 313405, hashes = { sha256 = "fb4790497369d134a07fc763cc88888c46f734abdd66f9fdf7865038bf3a8f40" } }, + { url = "https://files.pythonhosted.org/packages/af/22/7ab7b4ec3a1c1f03aef376af11d23b05abcca3fb31fbca1e7557053b1ba2/jiter-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-09-15T09:20:20Z, size = 347102, hashes = { sha256 = "6e2bbf24f16ba5ad4441a9845e40e4ea0cb9eed00e76ba94050664ef53ef4406" } }, + { url = "https://files.pythonhosted.org/packages/e5/d9/51cf35d92bea21f2051da8ca2328831589e67e2bf971e85b1a6e6c0d2030/jiter-0.11.0-cp39-cp39-macosx_10_12_x86_64.whl", upload-time = 2025-09-15T09:20:21Z, size = 312764, hashes = { sha256 = "719891c2fb7628a41adff4f2f54c19380a27e6fdfdb743c24680ef1a54c67bd0" } }, + { url = "https://files.pythonhosted.org/packages/da/48/eae309ce5c180faa1bb45e378a503717da22ceb2b0488f78e548f97c2b6b/jiter-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2025-09-15T09:20:22Z, size = 305861, hashes = { sha256 = "df7f1927cbdf34cb91262a5418ca06920fd42f1cf733936d863aeb29b45a14ef" } }, + { url = "https://files.pythonhosted.org/packages/83/4f/13b80e18b0331f0fecc09cb2f09f722530b9a395006941b01491fe58baea/jiter-0.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-09-15T09:20:23Z, size = 339507, hashes = { sha256 = "e71ae6d969d0c9bab336c5e9e2fabad31e74d823f19e3604eaf96d9a97f463df" } }, + { url = "https://files.pythonhosted.org/packages/97/6d/c2fd1512873d3f23d24537e97765e7090a00de466516aa442b994b064918/jiter-0.11.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-09-15T09:20:25Z, size = 363751, hashes = { sha256 = "5661469a7b2be25ade3a4bb6c21ffd1e142e13351a0759f264dfdd3ad99af1ab" } }, + { url = "https://files.pythonhosted.org/packages/b0/99/48d156c742e75d33b9c8be44b1142d233823be491acdb1009629e4109e6a/jiter-0.11.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-09-15T09:20:26Z, size = 488591, hashes = { sha256 = "76c15ef0d3d02f8b389066fa4c410a0b89e9cc6468a1f0674c5925d2f3c3e890" } }, + { url = "https://files.pythonhosted.org/packages/ba/fd/214452149f63847b791b1f6e9558f59e94674c47418c03e9787236ac8656/jiter-0.11.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-09-15T09:20:27Z, size = 378906, hashes = { sha256 = "63782a1350917a27817030716566ed3d5b3c731500fd42d483cbd7094e2c5b25" } }, + { url = "https://files.pythonhosted.org/packages/de/91/25e38fbbfc17111d7b70b24290a41d611cc2a27fa6cd0ed84ddae38ec3e6/jiter-0.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-09-15T09:20:28Z, size = 350288, hashes = { sha256 = "5a7092b699646a1ddc03a7b112622d9c066172627c7382659befb0d2996f1659" } }, + { url = "https://files.pythonhosted.org/packages/b5/d8/d6d2eefa9f0ff6ac6b725f5164a94f15bb4dee68584b5054043d735803da/jiter-0.11.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-09-15T09:20:30Z, size = 388442, hashes = { sha256 = "f637b8e818f6d75540f350a6011ce21252573c0998ea1b4365ee54b7672c23c5" } }, + { url = "https://files.pythonhosted.org/packages/ed/e4/cd7e27852de498d441a575a147ac7a15cf66768ae2cde8c43ea5b464c827/jiter-0.11.0-cp39-cp39-musllinux_1_1_aarch64.whl", upload-time = 2025-09-15T09:20:31Z, size = 518273, hashes = { sha256 = "a624d87719e1b5d09c15286eaee7e1532a40c692a096ea7ca791121365f548c1" } }, + { url = "https://files.pythonhosted.org/packages/77/a2/6681a9a503141752b33c92c58512ed8da13849ed3dbf88a3f8aba9bfb255/jiter-0.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", upload-time = 2025-09-15T09:20:32Z, size = 510254, hashes = { sha256 = "a9d0146d8d9b3995821bb586fc8256636258947c2f39da5bab709f3a28fb1a0b" } }, + { url = "https://files.pythonhosted.org/packages/38/32/df1a06f397074da35cf8fe79ec07da203358a2912b2a6349a0d4a88a1e0a/jiter-0.11.0-cp39-cp39-win32.whl", upload-time = 2025-09-15T09:20:34Z, size = 207076, hashes = { sha256 = "d067655a7cf0831eb8ec3e39cbd752995e9b69a2206df3535b3a067fac23b032" } }, + { url = "https://files.pythonhosted.org/packages/1d/91/11bc61fa76fd6197f5baa8576614852ee8586a16c2f25085edc6b47a369d/jiter-0.11.0-cp39-cp39-win_amd64.whl", upload-time = 2025-09-15T09:20:35Z, size = 206077, hashes = { sha256 = "f05d03775a11aaf132c447436983169958439f1219069abf24662a672851f94e" } }, + { url = "https://files.pythonhosted.org/packages/70/f3/ce100253c80063a7b8b406e1d1562657fd4b9b4e1b562db40e68645342fb/jiter-0.11.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-09-15T09:20:36Z, size = 336380, hashes = { sha256 = "902b43386c04739229076bd1c4c69de5d115553d982ab442a8ae82947c72ede7" } }, +] + +[[packages]] name = "joblib" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/08/8bd4a0250247861420a040b33ccf42f43c426ac91d99405374ef117e5872/joblib-1.5.0.tar.gz", hash = "sha256:d8757f955389a3dd7a23152e43bc297c2e0c2d3060056dad0feefc88a06939b5", size = 330234 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/d3/13ee227a148af1c693654932b8b0b02ed64af5e1f7406d56b088b57574cd/joblib-1.5.0-py3-none-any.whl", hash = "sha256:206144b320246485b712fc8cc51f017de58225fa8b414a1fe1764a7231aca491", size = 307682 }, -] +version = "1.5.2" +sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", upload-time = 2025-08-27T12:15:46Z, size = 331077, hashes = { sha256 = "3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", upload-time = 2025-08-27T12:15:45Z, size = 308396, hashes = { sha256 = "4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241" } }] -[[package]] +[[packages]] name = "kombu" -version = "5.5.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "amqp" }, - { name = "tzdata" }, - { name = "vine" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/60/0a/128b65651ed8120460fc5af754241ad595eac74993115ec0de4f2d7bc459/kombu-5.5.3.tar.gz", hash = "sha256:021a0e11fcfcd9b0260ef1fb64088c0e92beb976eb59c1dfca7ddd4ad4562ea2", size = 461784 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/35/1407fb0b2f5b07b50cbaf97fce09ad87d3bfefbf64f7171a8651cd8d2f68/kombu-5.5.3-py3-none-any.whl", hash = "sha256:5b0dbceb4edee50aa464f59469d34b97864be09111338cfb224a10b6a163909b", size = 209921 }, -] +version = "5.5.4" +sdist = { url = "https://files.pythonhosted.org/packages/0f/d3/5ff936d8319ac86b9c409f1501b07c426e6ad41966fedace9ef1b966e23f/kombu-5.5.4.tar.gz", upload-time = 2025-06-01T10:19:22Z, size = 461992, hashes = { sha256 = "886600168275ebeada93b888e831352fe578168342f0d1d5833d88ba0d847363" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", upload-time = 2025-06-01T10:19:20Z, size = 210034, hashes = { sha256 = "a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8" } }] -[[package]] -name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, -] - -[[package]] -name = "meshmind" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "celery", extra = ["redis"] }, - { name = "numpy" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "pymgclient" }, - { name = "pytest" }, - { name = "python-dotenv" }, - { name = "rapidfuzz" }, - { name = "scikit-learn" }, - { name = "sentence-transformers" }, - { name = "tiktoken" }, - { name = "typing-extensions" }, -] +[[packages]] +name = "markdown" +version = "3.9" +sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", upload-time = 2025-09-04T20:25:22Z, size = 364585, hashes = { sha256 = "d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", upload-time = 2025-09-04T20:25:21Z, size = 107441, hashes = { sha256 = "9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280" } }] -[package.metadata] -requires-dist = [ - { name = "celery", extras = ["redis"], specifier = ">=5.5.2" }, - { name = "numpy", specifier = ">=2.2.5" }, - { name = "openai", specifier = ">=1.78.0" }, - { name = "pydantic", specifier = ">=2.11.4" }, - { name = "pydantic-settings", specifier = ">=2.9.1" }, - { name = "pymgclient", specifier = ">=1.4.0" }, - { name = "pytest", specifier = ">=8.3.5" }, - { name = "python-dotenv", specifier = ">=1.1.0" }, - { name = "rapidfuzz", specifier = ">=3.13.0" }, - { name = "scikit-learn", specifier = ">=1.6.1" }, - { name = "sentence-transformers", specifier = ">=4.1.0" }, - { name = "tiktoken", specifier = ">=0.9.0" }, - { name = "typing-extensions", specifier = ">=4.13.2" }, -] +[[packages]] +name = "markupsafe" +version = "3.0.3" +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", upload-time = 2025-09-27T18:37:40Z, size = 80313, hashes = { sha256 = "722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", upload-time = 2025-09-27T18:36:05Z, size = 11631, hashes = { sha256 = "2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559" } }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-09-27T18:36:07Z, size = 12057, hashes = { sha256 = "e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419" } }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-27T18:36:08Z, size = 22050, hashes = { sha256 = "1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695" } }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-27T18:36:08Z, size = 20681, hashes = { sha256 = "f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591" } }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-09-27T18:36:10Z, size = 20705, hashes = { sha256 = "c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c" } }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-09-27T18:36:11Z, size = 21524, hashes = { sha256 = "0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f" } }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", upload-time = 2025-09-27T18:36:12Z, size = 20282, hashes = { sha256 = "d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6" } }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-09-27T18:36:13Z, size = 20745, hashes = { sha256 = "177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1" } }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", upload-time = 2025-09-27T18:36:14Z, size = 14571, hashes = { sha256 = "2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa" } }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", upload-time = 2025-09-27T18:36:16Z, size = 15056, hashes = { sha256 = "c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8" } }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", upload-time = 2025-09-27T18:36:17Z, size = 13932, hashes = { sha256 = "e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1" } }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", upload-time = 2025-09-27T18:36:18Z, size = 11631, hashes = { sha256 = "1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad" } }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-09-27T18:36:19Z, size = 12058, hashes = { sha256 = "4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a" } }, + { 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", upload-time = 2025-09-27T18:36:20Z, size = 24287, hashes = { sha256 = "6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50" } }, + { 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", upload-time = 2025-09-27T18:36:22Z, size = 22940, hashes = { sha256 = "0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf" } }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-09-27T18:36:23Z, size = 21887, hashes = { sha256 = "bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f" } }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-09-27T18:36:24Z, size = 23692, hashes = { sha256 = "068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a" } }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", upload-time = 2025-09-27T18:36:25Z, size = 21471, hashes = { sha256 = "7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115" } }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-09-27T18:36:27Z, size = 22923, hashes = { sha256 = "f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a" } }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", upload-time = 2025-09-27T18:36:28Z, size = 14572, hashes = { sha256 = "0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19" } }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", upload-time = 2025-09-27T18:36:29Z, size = 15077, hashes = { sha256 = "de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01" } }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", upload-time = 2025-09-27T18:36:29Z, size = 13876, hashes = { sha256 = "3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c" } }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2025-09-27T18:36:30Z, size = 11615, hashes = { sha256 = "d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e" } }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-09-27T18:36:31Z, size = 12020, hashes = { sha256 = "1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce" } }, + { 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", upload-time = 2025-09-27T18:36:32Z, size = 24332, hashes = { sha256 = "3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d" } }, + { 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", upload-time = 2025-09-27T18:36:33Z, size = 22947, hashes = { sha256 = "d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d" } }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-09-27T18:36:35Z, size = 21962, hashes = { sha256 = "94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a" } }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-09-27T18:36:36Z, size = 23760, hashes = { sha256 = "be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b" } }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", upload-time = 2025-09-27T18:36:36Z, size = 21529, hashes = { sha256 = "83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f" } }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-09-27T18:36:37Z, size = 23015, hashes = { sha256 = "77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b" } }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", upload-time = 2025-09-27T18:36:38Z, size = 14540, hashes = { sha256 = "d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d" } }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", upload-time = 2025-09-27T18:36:39Z, size = 15105, hashes = { sha256 = "26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c" } }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", upload-time = 2025-09-27T18:36:40Z, size = 13906, hashes = { sha256 = "35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f" } }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-09-27T18:36:41Z, size = 11622, hashes = { sha256 = "e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795" } }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-09-27T18:36:43Z, size = 12029, hashes = { sha256 = "116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219" } }, + { 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", upload-time = 2025-09-27T18:36:44Z, size = 24374, hashes = { sha256 = "133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6" } }, + { 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", upload-time = 2025-09-27T18:36:45Z, size = 22980, hashes = { sha256 = "ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676" } }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-09-27T18:36:46Z, size = 21990, hashes = { sha256 = "509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9" } }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-09-27T18:36:47Z, size = 23784, hashes = { sha256 = "a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1" } }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", upload-time = 2025-09-27T18:36:48Z, size = 21588, hashes = { sha256 = "795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc" } }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-09-27T18:36:49Z, size = 23041, hashes = { sha256 = "8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12" } }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", upload-time = 2025-09-27T18:36:51Z, size = 14543, hashes = { sha256 = "bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed" } }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", upload-time = 2025-09-27T18:36:52Z, size = 15113, hashes = { sha256 = "9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5" } }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", upload-time = 2025-09-27T18:36:53Z, size = 13911, hashes = { sha256 = "7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485" } }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", upload-time = 2025-09-27T18:36:54Z, size = 11658, hashes = { sha256 = "218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73" } }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-09-27T18:36:55Z, size = 12066, hashes = { sha256 = "3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37" } }, + { 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", upload-time = 2025-09-27T18:36:56Z, size = 25639, hashes = { sha256 = "4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19" } }, + { 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", upload-time = 2025-09-27T18:36:57Z, size = 23569, hashes = { sha256 = "8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025" } }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-09-27T18:36:58Z, size = 23284, hashes = { sha256 = "b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6" } }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", upload-time = 2025-09-27T18:36:59Z, size = 24801, hashes = { sha256 = "9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f" } }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", upload-time = 2025-09-27T18:37:00Z, size = 22769, hashes = { sha256 = "12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb" } }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", upload-time = 2025-09-27T18:37:01Z, size = 23642, hashes = { sha256 = "8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009" } }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", upload-time = 2025-09-27T18:37:02Z, size = 14612, hashes = { sha256 = "69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354" } }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", upload-time = 2025-09-27T18:37:03Z, size = 15200, hashes = { sha256 = "1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218" } }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", upload-time = 2025-09-27T18:37:04Z, size = 13973, hashes = { sha256 = "ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287" } }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", upload-time = 2025-09-27T18:37:06Z, size = 11619, hashes = { sha256 = "eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe" } }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-09-27T18:37:07Z, size = 12029, hashes = { sha256 = "c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026" } }, + { 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", upload-time = 2025-09-27T18:37:09Z, size = 24408, hashes = { sha256 = "f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737" } }, + { 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", upload-time = 2025-09-27T18:37:10Z, size = 23005, hashes = { sha256 = "457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97" } }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-09-27T18:37:11Z, size = 22048, hashes = { sha256 = "e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d" } }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-09-27T18:37:12Z, size = 23821, hashes = { sha256 = "ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda" } }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", upload-time = 2025-09-27T18:37:13Z, size = 21606, hashes = { sha256 = "0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf" } }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-09-27T18:37:14Z, size = 23043, hashes = { sha256 = "2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe" } }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", upload-time = 2025-09-27T18:37:15Z, size = 14747, hashes = { sha256 = "729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9" } }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", upload-time = 2025-09-27T18:37:16Z, size = 15341, hashes = { sha256 = "bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581" } }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", upload-time = 2025-09-27T18:37:17Z, size = 14073, hashes = { sha256 = "5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4" } }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", upload-time = 2025-09-27T18:37:18Z, size = 11661, hashes = { sha256 = "1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab" } }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-09-27T18:37:19Z, size = 12069, hashes = { sha256 = "1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175" } }, + { 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", upload-time = 2025-09-27T18:37:20Z, size = 25670, hashes = { sha256 = "1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634" } }, + { 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", upload-time = 2025-09-27T18:37:21Z, size = 23598, hashes = { sha256 = "fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50" } }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-09-27T18:37:22Z, size = 23261, hashes = { sha256 = "f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e" } }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", upload-time = 2025-09-27T18:37:23Z, size = 24835, hashes = { sha256 = "e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5" } }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", upload-time = 2025-09-27T18:37:24Z, size = 22733, hashes = { sha256 = "f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523" } }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", upload-time = 2025-09-27T18:37:25Z, size = 23672, hashes = { sha256 = "5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc" } }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", upload-time = 2025-09-27T18:37:26Z, size = 14819, hashes = { sha256 = "915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d" } }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", upload-time = 2025-09-27T18:37:27Z, size = 15426, hashes = { sha256 = "4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9" } }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", upload-time = 2025-09-27T18:37:28Z, size = 14146, hashes = { sha256 = "32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa" } }, + { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", upload-time = 2025-09-27T18:37:29Z, size = 11623, hashes = { sha256 = "15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26" } }, + { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2025-09-27T18:37:30Z, size = 12049, hashes = { sha256 = "f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc" } }, + { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-27T18:37:31Z, size = 21923, hashes = { sha256 = "0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c" } }, + { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-27T18:37:32Z, size = 20543, hashes = { sha256 = "e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42" } }, + { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2025-09-27T18:37:33Z, size = 20585, hashes = { sha256 = "949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b" } }, + { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", upload-time = 2025-09-27T18:37:34Z, size = 21387, hashes = { sha256 = "3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758" } }, + { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", upload-time = 2025-09-27T18:37:35Z, size = 20133, hashes = { sha256 = "591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2" } }, + { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", upload-time = 2025-09-27T18:37:36Z, size = 20588, hashes = { sha256 = "a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d" } }, + { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", upload-time = 2025-09-27T18:37:37Z, size = 14566, hashes = { sha256 = "df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7" } }, + { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", upload-time = 2025-09-27T18:37:38Z, size = 15053, hashes = { sha256 = "7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e" } }, + { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", upload-time = 2025-09-27T18:37:39Z, size = 13928, hashes = { sha256 = "38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8" } }, +] + +[[packages]] +name = "mergedeep" +version = "1.3.4" +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", upload-time = 2021-02-05T18:55:30Z, size = 4661, hashes = { sha256 = "0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", upload-time = 2021-02-05T18:55:29Z, size = 6354, hashes = { sha256 = "70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307" } }] + +[[packages]] +name = "mkdocs" +version = "1.6.1" +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", upload-time = 2024-08-30T12:24:06Z, size = 3889159, hashes = { sha256 = "7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", upload-time = 2024-08-30T12:24:05Z, size = 3864451, hashes = { sha256 = "db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e" } }] + +[[packages]] +name = "mkdocs-get-deps" +version = "0.2.0" +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", upload-time = 2023-11-20T17:51:09Z, size = 10239, hashes = { sha256 = "162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", upload-time = 2023-11-20T17:51:08Z, size = 9521, hashes = { sha256 = "2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134" } }] + +[[packages]] +name = "mkdocs-material" +version = "9.6.22" +sdist = { url = "https://files.pythonhosted.org/packages/5f/5d/317e37b6c43325cb376a1d6439df9cc743b8ee41c84603c2faf7286afc82/mkdocs_material-9.6.22.tar.gz", upload-time = 2025-10-15T09:21:15Z, size = 4044968, hashes = { sha256 = "87c158b0642e1ada6da0cbd798a3389b0bc5516b90e5ece4a0fb939f00bacd1c" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl", upload-time = 2025-10-15T09:21:12Z, size = 9206252, hashes = { sha256 = "14ac5f72d38898b2f98ac75a5531aaca9366eaa427b0f49fc2ecf04d99b7ad84" } }] + +[[packages]] +name = "mkdocs-material-extensions" +version = "1.3.1" +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", upload-time = 2023-11-22T19:09:45Z, size = 11847, hashes = { sha256 = "10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", upload-time = 2023-11-22T19:09:43Z, size = 8728, hashes = { sha256 = "adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31" } }] -[[package]] +[[packages]] name = "mpmath" version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", upload-time = 2023-03-07T16:47:11Z, size = 508106, hashes = { sha256 = "7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", upload-time = 2023-03-07T16:47:09Z, size = 536198, hashes = { sha256 = "a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c" } }] + +[[packages]] +name = "neo4j" +version = "6.0.2" +sdist = { url = "https://files.pythonhosted.org/packages/eb/34/485ab7c0252bd5d9c9ff0672f61153a8007490af2069f664d8766709c7ba/neo4j-6.0.2.tar.gz", upload-time = 2025-10-02T11:31:06Z, size = 240139, hashes = { sha256 = "c98734c855b457e7a976424dc04446d652838d00907d250d6e9a595e88892378" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/75/4e/11813da186859070b0512e8071dac4796624ac4dc28e25e7c530df730d23/neo4j-6.0.2-py3-none-any.whl", upload-time = 2025-10-02T11:31:04Z, size = 325761, hashes = { sha256 = "dc3fc1c99f6da2293d9deefead1e31dd7429bbb513eccf96e4134b7dbf770243" } }] -[[package]] +[[packages]] name = "networkx" -version = "3.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, -] +version = "3.5" +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", upload-time = 2025-05-29T11:35:07Z, size = 2471065, hashes = { sha256 = "d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", upload-time = 2025-05-29T11:35:04Z, size = 2034406, hashes = { sha256 = "0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec" } }] -[[package]] -name = "numpy" -version = "2.2.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102 }, - { url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709 }, - { url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173 }, - { url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502 }, - { url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417 }, - { url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807 }, - { url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611 }, - { url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747 }, - { url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594 }, - { url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356 }, - { url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778 }, - { url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279 }, - { url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247 }, - { url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087 }, - { url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964 }, - { url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214 }, - { url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788 }, - { url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672 }, - { url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102 }, - { url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096 }, -] +[[packages]] +name = "nodeenv" +version = "1.9.1" +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", upload-time = 2024-06-04T18:44:11Z, size = 47437, hashes = { sha256 = "6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", upload-time = 2024-06-04T18:44:08Z, size = 22314, hashes = { sha256 = "ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9" } }] -[[package]] +[[packages]] +name = "numpy" +version = "2.3.4" +sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", upload-time = 2025-10-15T16:18:11Z, size = 20582187, hashes = { sha256 = "a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e7/0e07379944aa8afb49a556a2b54587b828eb41dc9adc56fb7615b678ca53/numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl", upload-time = 2025-10-15T16:15:19Z, size = 21259519, hashes = { sha256 = "e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb" } }, + { url = "https://files.pythonhosted.org/packages/d0/cb/5a69293561e8819b09e34ed9e873b9a82b5f2ade23dce4c51dc507f6cfe1/numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-10-15T16:15:23Z, size = 14452796, hashes = { sha256 = "7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f" } }, + { url = "https://files.pythonhosted.org/packages/e4/04/ff11611200acd602a1e5129e36cfd25bf01ad8e5cf927baf2e90236eb02e/numpy-2.3.4-cp311-cp311-macosx_14_0_arm64.whl", upload-time = 2025-10-15T16:15:25Z, size = 5381639, hashes = { sha256 = "1b219560ae2c1de48ead517d085bc2d05b9433f8e49d0955c82e8cd37bd7bf36" } }, + { url = "https://files.pythonhosted.org/packages/ea/77/e95c757a6fe7a48d28a009267408e8aa382630cc1ad1db7451b3bc21dbb4/numpy-2.3.4-cp311-cp311-macosx_14_0_x86_64.whl", upload-time = 2025-10-15T16:15:27Z, size = 6914296, hashes = { sha256 = "bafa7d87d4c99752d07815ed7a2c0964f8ab311eb8168f41b910bd01d15b6032" } }, + { url = "https://files.pythonhosted.org/packages/a3/d2/137c7b6841c942124eae921279e5c41b1c34bab0e6fc60c7348e69afd165/numpy-2.3.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T16:15:29Z, size = 14591904, hashes = { sha256 = "36dc13af226aeab72b7abad501d370d606326a0029b9f435eacb3b8c94b8a8b7" } }, + { url = "https://files.pythonhosted.org/packages/bb/32/67e3b0f07b0aba57a078c4ab777a9e8e6bc62f24fb53a2337f75f9691699/numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T16:15:31Z, size = 16939602, hashes = { sha256 = "a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda" } }, + { url = "https://files.pythonhosted.org/packages/95/22/9639c30e32c93c4cee3ccdb4b09c2d0fbff4dcd06d36b357da06146530fb/numpy-2.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T16:15:33Z, size = 16372661, hashes = { sha256 = "9984bd645a8db6ca15d850ff996856d8762c51a2239225288f08f9050ca240a0" } }, + { url = "https://files.pythonhosted.org/packages/12/e9/a685079529be2b0156ae0c11b13d6be647743095bb51d46589e95be88086/numpy-2.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T16:15:36Z, size = 18884682, hashes = { sha256 = "64c5825affc76942973a70acf438a8ab618dbd692b84cd5ec40a0a0509edc09a" } }, + { url = "https://files.pythonhosted.org/packages/cf/85/f6f00d019b0cc741e64b4e00ce865a57b6bed945d1bbeb1ccadbc647959b/numpy-2.3.4-cp311-cp311-win32.whl", upload-time = 2025-10-15T16:15:38Z, size = 6570076, hashes = { sha256 = "ed759bf7a70342f7817d88376eb7142fab9fef8320d6019ef87fae05a99874e1" } }, + { url = "https://files.pythonhosted.org/packages/7d/10/f8850982021cb90e2ec31990291f9e830ce7d94eef432b15066e7cbe0bec/numpy-2.3.4-cp311-cp311-win_amd64.whl", upload-time = 2025-10-15T16:15:40Z, size = 13089358, hashes = { sha256 = "faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996" } }, + { url = "https://files.pythonhosted.org/packages/d1/ad/afdd8351385edf0b3445f9e24210a9c3971ef4de8fd85155462fc4321d79/numpy-2.3.4-cp311-cp311-win_arm64.whl", upload-time = 2025-10-15T16:15:42Z, size = 10462292, hashes = { sha256 = "4c01835e718bcebe80394fd0ac66c07cbb90147ebbdad3dcecd3f25de2ae7e2c" } }, + { url = "https://files.pythonhosted.org/packages/96/7a/02420400b736f84317e759291b8edaeee9dc921f72b045475a9cbdb26b17/numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T16:15:44Z, size = 20957727, hashes = { sha256 = "ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11" } }, + { url = "https://files.pythonhosted.org/packages/18/90/a014805d627aa5750f6f0e878172afb6454552da929144b3c07fcae1bb13/numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-10-15T16:15:47Z, size = 14187262, hashes = { sha256 = "c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9" } }, + { url = "https://files.pythonhosted.org/packages/c7/e4/0a94b09abe89e500dc748e7515f21a13e30c5c3fe3396e6d4ac108c25fca/numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl", upload-time = 2025-10-15T16:15:50Z, size = 5115992, hashes = { sha256 = "f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667" } }, + { url = "https://files.pythonhosted.org/packages/88/dd/db77c75b055c6157cbd4f9c92c4458daef0dd9cbe6d8d2fe7f803cb64c37/numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl", upload-time = 2025-10-15T16:15:52Z, size = 6648672, hashes = { sha256 = "8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef" } }, + { url = "https://files.pythonhosted.org/packages/e1/e6/e31b0d713719610e406c0ea3ae0d90760465b086da8783e2fd835ad59027/numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T16:15:54Z, size = 14284156, hashes = { sha256 = "77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e" } }, + { url = "https://files.pythonhosted.org/packages/f9/58/30a85127bfee6f108282107caf8e06a1f0cc997cb6b52cdee699276fcce4/numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T16:15:56Z, size = 16641271, hashes = { sha256 = "4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a" } }, + { url = "https://files.pythonhosted.org/packages/06/f2/2e06a0f2adf23e3ae29283ad96959267938d0efd20a2e25353b70065bfec/numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T16:15:59Z, size = 16059531, hashes = { sha256 = "65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16" } }, + { url = "https://files.pythonhosted.org/packages/b0/e7/b106253c7c0d5dc352b9c8fab91afd76a93950998167fa3e5afe4ef3a18f/numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T16:16:01Z, size = 18578983, hashes = { sha256 = "dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786" } }, + { url = "https://files.pythonhosted.org/packages/73/e3/04ecc41e71462276ee867ccbef26a4448638eadecf1bc56772c9ed6d0255/numpy-2.3.4-cp312-cp312-win32.whl", upload-time = 2025-10-15T16:16:03Z, size = 6291380, hashes = { sha256 = "a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc" } }, + { url = "https://files.pythonhosted.org/packages/3d/a8/566578b10d8d0e9955b1b6cd5db4e9d4592dd0026a941ff7994cedda030a/numpy-2.3.4-cp312-cp312-win_amd64.whl", upload-time = 2025-10-15T16:16:05Z, size = 12787999, hashes = { sha256 = "985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32" } }, + { url = "https://files.pythonhosted.org/packages/58/22/9c903a957d0a8071b607f5b1bff0761d6e608b9a965945411f867d515db1/numpy-2.3.4-cp312-cp312-win_arm64.whl", upload-time = 2025-10-15T16:16:07Z, size = 10197412, hashes = { sha256 = "4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db" } }, + { url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T16:16:10Z, size = 20950335, hashes = { sha256 = "c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966" } }, + { url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-10-15T16:16:12Z, size = 14179878, hashes = { sha256 = "a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3" } }, + { url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", upload-time = 2025-10-15T16:16:14Z, size = 5108673, hashes = { sha256 = "3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197" } }, + { url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", upload-time = 2025-10-15T16:16:16Z, size = 6641438, hashes = { sha256 = "043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e" } }, + { url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T16:16:18Z, size = 14281290, hashes = { sha256 = "4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7" } }, + { url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T16:16:21Z, size = 16636543, hashes = { sha256 = "fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953" } }, + { url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T16:16:23Z, size = 16056117, hashes = { sha256 = "40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37" } }, + { url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T16:16:27Z, size = 18577788, hashes = { sha256 = "ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd" } }, + { url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", upload-time = 2025-10-15T16:16:29Z, size = 6282620, hashes = { sha256 = "e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646" } }, + { url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", upload-time = 2025-10-15T16:16:31Z, size = 12784672, hashes = { sha256 = "56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d" } }, + { url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", upload-time = 2025-10-15T16:16:33Z, size = 10196702, hashes = { sha256 = "a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc" } }, + { url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T16:16:36Z, size = 21049003, hashes = { sha256 = "86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879" } }, + { url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-10-15T16:16:39Z, size = 14302980, hashes = { sha256 = "838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562" } }, + { url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", upload-time = 2025-10-15T16:16:41Z, size = 5231472, hashes = { sha256 = "d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a" } }, + { url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", upload-time = 2025-10-15T16:16:43Z, size = 6739342, hashes = { sha256 = "84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6" } }, + { url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T16:16:46Z, size = 14354338, hashes = { sha256 = "817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7" } }, + { url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T16:16:48Z, size = 16702392, hashes = { sha256 = "85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0" } }, + { url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T16:16:51Z, size = 16134998, hashes = { sha256 = "2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f" } }, + { url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T16:16:53Z, size = 18651574, hashes = { sha256 = "035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64" } }, + { url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", upload-time = 2025-10-15T16:16:55Z, size = 6413135, hashes = { sha256 = "fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb" } }, + { url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", upload-time = 2025-10-15T16:16:57Z, size = 12928582, hashes = { sha256 = "15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c" } }, + { url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", upload-time = 2025-10-15T16:17:00Z, size = 10266691, hashes = { sha256 = "b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40" } }, + { url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", upload-time = 2025-10-15T16:17:02Z, size = 20955580, hashes = { sha256 = "81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e" } }, + { url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-10-15T16:17:04Z, size = 14188056, hashes = { sha256 = "7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff" } }, + { url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", upload-time = 2025-10-15T16:17:07Z, size = 5116555, hashes = { sha256 = "62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f" } }, + { url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", upload-time = 2025-10-15T16:17:09Z, size = 6643581, hashes = { sha256 = "9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b" } }, + { url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T16:17:11Z, size = 14299186, hashes = { sha256 = "bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7" } }, + { url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T16:17:14Z, size = 16638601, hashes = { sha256 = "d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2" } }, + { url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T16:17:17Z, size = 16074219, hashes = { sha256 = "15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52" } }, + { url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T16:17:19Z, size = 18576702, hashes = { sha256 = "85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26" } }, + { url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", upload-time = 2025-10-15T16:17:22Z, size = 6337136, hashes = { sha256 = "e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc" } }, + { url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", upload-time = 2025-10-15T16:17:24Z, size = 12920542, hashes = { sha256 = "3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9" } }, + { url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", upload-time = 2025-10-15T16:17:26Z, size = 10480213, hashes = { sha256 = "6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868" } }, + { url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", upload-time = 2025-10-15T16:17:29Z, size = 21052280, hashes = { sha256 = "22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec" } }, + { url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-10-15T16:17:32Z, size = 14302930, hashes = { sha256 = "9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3" } }, + { url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", upload-time = 2025-10-15T16:17:34Z, size = 5231504, hashes = { sha256 = "0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365" } }, + { url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", upload-time = 2025-10-15T16:17:36Z, size = 6739405, hashes = { sha256 = "8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252" } }, + { url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T16:17:38Z, size = 14354866, hashes = { sha256 = "5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e" } }, + { url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T16:17:41Z, size = 16703296, hashes = { sha256 = "85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0" } }, + { url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T16:17:43Z, size = 16136046, hashes = { sha256 = "04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0" } }, + { url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T16:17:46Z, size = 18652691, hashes = { sha256 = "e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f" } }, + { url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", upload-time = 2025-10-15T16:17:48Z, size = 6485782, hashes = { sha256 = "863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d" } }, + { url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", upload-time = 2025-10-15T16:17:50Z, size = 13113301, hashes = { sha256 = "962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6" } }, + { url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", upload-time = 2025-10-15T16:17:53Z, size = 10547532, hashes = { sha256 = "8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29" } }, + { url = "https://files.pythonhosted.org/packages/b1/b6/64898f51a86ec88ca1257a59c1d7fd077b60082a119affefcdf1dd0df8ca/numpy-2.3.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", upload-time = 2025-10-15T16:17:55Z, size = 21131552, hashes = { sha256 = "6e274603039f924c0fe5cb73438fa9246699c78a6df1bd3decef9ae592ae1c05" } }, + { url = "https://files.pythonhosted.org/packages/ce/4c/f135dc6ebe2b6a3c77f4e4838fa63d350f85c99462012306ada1bd4bc460/numpy-2.3.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", upload-time = 2025-10-15T16:17:58Z, size = 14377796, hashes = { sha256 = "d149aee5c72176d9ddbc6803aef9c0f6d2ceeea7626574fc68518da5476fa346" } }, + { url = "https://files.pythonhosted.org/packages/d0/a4/f33f9c23fcc13dd8412fc8614559b5b797e0aba9d8e01dfa8bae10c84004/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", upload-time = 2025-10-15T16:18:00Z, size = 5306904, hashes = { sha256 = "6d34ed9db9e6395bb6cd33286035f73a59b058169733a9db9f85e650b88df37e" } }, + { url = "https://files.pythonhosted.org/packages/28/af/c44097f25f834360f9fb960fa082863e0bad14a42f36527b2a121abdec56/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", upload-time = 2025-10-15T16:18:02Z, size = 6819682, hashes = { sha256 = "fdebe771ca06bb8d6abce84e51dca9f7921fe6ad34a0c914541b063e9a68928b" } }, + { url = "https://files.pythonhosted.org/packages/c5/8c/cd283b54c3c2b77e188f63e23039844f56b23bba1712318288c13fe86baf/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T16:18:04Z, size = 14422300, hashes = { sha256 = "957e92defe6c08211eb77902253b14fe5b480ebc5112bc741fd5e9cd0608f847" } }, + { url = "https://files.pythonhosted.org/packages/b0/f0/8404db5098d92446b3e3695cf41c6f0ecb703d701cb0b7566ee2177f2eee/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T16:18:06Z, size = 16760806, hashes = { sha256 = "13b9062e4f5c7ee5c7e5be96f29ba71bc5a37fed3d1d77c37390ae00724d296d" } }, + { url = "https://files.pythonhosted.org/packages/95/8e/2844c3959ce9a63acc7c8e50881133d86666f0420bcde695e115ced0920f/numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", upload-time = 2025-10-15T16:18:09Z, size = 12973130, hashes = { sha256 = "81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f" } }, +] + +[[packages]] name = "nvidia-cublas-cu12" -version = "12.6.4.1" -source = { registry = "https://pypi.org/simple" } +version = "12.8.4.1" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/af/eb/ff4b8c503fa1f1796679dce648854d58751982426e4e4b37d6fce49d259c/nvidia_cublas_cu12-12.6.4.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08ed2686e9875d01b58e3cb379c6896df8e76c75e0d4a7f7dace3d7b6d9ef8eb", size = 393138322 }, + { url = "https://files.pythonhosted.org/packages/29/99/db44d685f0e257ff0e213ade1964fc459b4a690a73293220e98feb3307cf/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_aarch64.whl", upload-time = 2025-03-07T01:43:53Z, size = 590537124, hashes = { sha256 = "b86f6dd8935884615a0683b663891d43781b819ac4f2ba2b0c9604676af346d0" } }, + { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", upload-time = 2025-03-07T01:44:31Z, size = 594346921, hashes = { sha256 = "8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142" } }, + { url = "https://files.pythonhosted.org/packages/70/61/7d7b3c70186fb651d0fbd35b01dbfc8e755f69fd58f817f3d0f642df20c3/nvidia_cublas_cu12-12.8.4.1-py3-none-win_amd64.whl", upload-time = 2025-03-07T01:53:30Z, size = 567544208, hashes = { sha256 = "47e9b82132fa8d2b4944e708049229601448aaad7e6f296f630f2d1a32de35af" } }, ] -[[package]] +[[packages]] name = "nvidia-cuda-cupti-cu12" -version = "12.6.80" -source = { registry = "https://pypi.org/simple" } +version = "12.8.90" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/49/60/7b6497946d74bcf1de852a21824d63baad12cd417db4195fc1bfe59db953/nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6768bad6cab4f19e8292125e5f1ac8aa7d1718704012a0e3272a6f61c4bce132", size = 8917980 }, - { url = "https://files.pythonhosted.org/packages/a5/24/120ee57b218d9952c379d1e026c4479c9ece9997a4fb46303611ee48f038/nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a3eff6cdfcc6a4c35db968a06fcadb061cbc7d6dde548609a941ff8701b98b73", size = 8917972 }, + { url = "https://files.pythonhosted.org/packages/d5/1f/b3bd73445e5cb342727fd24fe1f7b748f690b460acadc27ea22f904502c8/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-03-07T01:40:10Z, size = 9533318, hashes = { sha256 = "4412396548808ddfed3f17a467b104ba7751e6b58678a4b840675c56d21cf7ed" } }, + { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-03-07T01:40:21Z, size = 10248621, hashes = { sha256 = "ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182" } }, + { url = "https://files.pythonhosted.org/packages/41/bc/83f5426095d93694ae39fe1311431b5d5a9bb82e48bf0dd8e19be2765942/nvidia_cuda_cupti_cu12-12.8.90-py3-none-win_amd64.whl", upload-time = 2025-03-07T01:51:11Z, size = 7015759, hashes = { sha256 = "bb479dcdf7e6d4f8b0b01b115260399bf34154a1a2e9fe11c85c517d87efd98e" } }, ] -[[package]] +[[packages]] name = "nvidia-cuda-nvrtc-cu12" -version = "12.6.77" -source = { registry = "https://pypi.org/simple" } +version = "12.8.93" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/75/2e/46030320b5a80661e88039f59060d1790298b4718944a65a7f2aeda3d9e9/nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:35b0cc6ee3a9636d5409133e79273ce1f3fd087abb0532d2d2e8fff1fe9efc53", size = 23650380 }, + { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", upload-time = 2025-03-07T01:42:13Z, size = 88040029, hashes = { sha256 = "a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994" } }, + { url = "https://files.pythonhosted.org/packages/eb/d1/e50d0acaab360482034b84b6e27ee83c6738f7d32182b987f9c7a4e32962/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-03-07T01:41:59Z, size = 43106076, hashes = { sha256 = "fc1fec1e1637854b4c0a65fb9a8346b51dd9ee69e61ebaccc82058441f15bce8" } }, + { url = "https://files.pythonhosted.org/packages/45/51/52a3d84baa2136cc8df15500ad731d74d3a1114d4c123e043cb608d4a32b/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-win_amd64.whl", upload-time = 2025-03-07T01:52:13Z, size = 73586838, hashes = { sha256 = "7a4b6b2904850fe78e0bd179c4b655c404d4bb799ef03ddc60804247099ae909" } }, ] -[[package]] +[[packages]] name = "nvidia-cuda-runtime-cu12" -version = "12.6.77" -source = { registry = "https://pypi.org/simple" } +version = "12.8.90" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/23/e717c5ac26d26cf39a27fbc076240fad2e3b817e5889d671b67f4f9f49c5/nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba3b56a4f896141e25e19ab287cd71e52a6a0f4b29d0d31609f60e3b4d5219b7", size = 897690 }, - { url = "https://files.pythonhosted.org/packages/f0/62/65c05e161eeddbafeca24dc461f47de550d9fa8a7e04eb213e32b55cfd99/nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a84d15d5e1da416dd4774cb42edf5e954a3e60cc945698dc1d5be02321c44dc8", size = 897678 }, + { url = "https://files.pythonhosted.org/packages/7c/75/f865a3b236e4647605ea34cc450900854ba123834a5f1598e160b9530c3a/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-03-07T01:39:43Z, size = 965265, hashes = { sha256 = "52bf7bbee900262ffefe5e9d5a2a69a30d97e2bc5bb6cc866688caa976966e3d" } }, + { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-03-07T01:40:01Z, size = 954765, hashes = { sha256 = "adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90" } }, + { url = "https://files.pythonhosted.org/packages/30/a5/a515b7600ad361ea14bfa13fb4d6687abf500adc270f19e89849c0590492/nvidia_cuda_runtime_cu12-12.8.90-py3-none-win_amd64.whl", upload-time = 2025-03-07T01:51:01Z, size = 944318, hashes = { sha256 = "c0c6027f01505bfed6c3b21ec546f69c687689aad5f1a377554bc6ca4aa993a8" } }, ] -[[package]] +[[packages]] name = "nvidia-cudnn-cu12" -version = "9.5.1.17" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-cublas-cu12" }, -] +version = "9.10.2.21" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/78/4535c9c7f859a64781e43c969a3a7e84c54634e319a996d43ef32ce46f83/nvidia_cudnn_cu12-9.5.1.17-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:30ac3869f6db17d170e0e556dd6cc5eee02647abc31ca856634d5a40f82c15b2", size = 570988386 }, + { url = "https://files.pythonhosted.org/packages/fa/41/e79269ce215c857c935fd86bcfe91a451a584dfc27f1e068f568b9ad1ab7/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_aarch64.whl", upload-time = 2025-06-06T21:52:51Z, size = 705026878, hashes = { sha256 = "c9132cc3f8958447b4910a1720036d9eff5928cc3179b0a51fb6d167c6cc87d8" } }, + { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", upload-time = 2025-06-06T21:54:08Z, size = 706758467, hashes = { sha256 = "949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8" } }, + { url = "https://files.pythonhosted.org/packages/3d/90/0bd6e586701b3a890fd38aa71c387dab4883d619d6e5ad912ccbd05bfd67/nvidia_cudnn_cu12-9.10.2.21-py3-none-win_amd64.whl", upload-time = 2025-06-06T21:55:18Z, size = 692992268, hashes = { sha256 = "c6288de7d63e6cf62988f0923f96dc339cea362decb1bf5b3141883392a7d65e" } }, ] -[[package]] +[[packages]] name = "nvidia-cufft-cu12" -version = "11.3.0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, -] +version = "11.3.3.83" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/16/73727675941ab8e6ffd86ca3a4b7b47065edcca7a997920b831f8147c99d/nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ccba62eb9cef5559abd5e0d54ceed2d9934030f51163df018532142a8ec533e5", size = 200221632 }, - { url = "https://files.pythonhosted.org/packages/60/de/99ec247a07ea40c969d904fc14f3a356b3e2a704121675b75c366b694ee1/nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:768160ac89f6f7b459bee747e8d175dbf53619cfe74b2a5636264163138013ca", size = 200221622 }, + { url = "https://files.pythonhosted.org/packages/60/bc/7771846d3a0272026c416fbb7e5f4c1f146d6d80704534d0b187dd6f4800/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-03-07T01:44:56Z, size = 193109211, hashes = { sha256 = "848ef7224d6305cdb2a4df928759dca7b1201874787083b6e7550dd6765ce69a" } }, + { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-03-07T01:45:27Z, size = 193118695, hashes = { sha256 = "4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74" } }, + { url = "https://files.pythonhosted.org/packages/7d/ec/ce1629f1e478bb5ccd208986b5f9e0316a78538dd6ab1d0484f012f8e2a1/nvidia_cufft_cu12-11.3.3.83-py3-none-win_amd64.whl", upload-time = 2025-03-07T01:53:57Z, size = 192216559, hashes = { sha256 = "7a64a98ef2a7c47f905aaf8931b69a3a43f27c55530c698bb2ed7c75c0b42cb7" } }, ] -[[package]] +[[packages]] name = "nvidia-cufile-cu12" -version = "1.11.1.6" -source = { registry = "https://pypi.org/simple" } +version = "1.13.1.3" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/66/cc9876340ac68ae71b15c743ddb13f8b30d5244af344ec8322b449e35426/nvidia_cufile_cu12-1.11.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc23469d1c7e52ce6c1d55253273d32c565dd22068647f3aa59b3c6b005bf159", size = 1142103 }, + { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-03-07T01:45:50Z, size = 1197834, hashes = { sha256 = "1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc" } }, + { url = "https://files.pythonhosted.org/packages/1e/f5/5607710447a6fe9fd9b3283956fceeee8a06cda1d2f56ce31371f595db2a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux_2_27_aarch64.whl", upload-time = 2025-03-07T01:45:41Z, size = 1120705, hashes = { sha256 = "4beb6d4cce47c1a0f1013d72e02b0994730359e17801d395bdcbf20cfb3bb00a" } }, ] -[[package]] +[[packages]] name = "nvidia-curand-cu12" -version = "10.3.7.77" -source = { registry = "https://pypi.org/simple" } +version = "10.3.9.90" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/73/1b/44a01c4e70933637c93e6e1a8063d1e998b50213a6b65ac5a9169c47e98e/nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a42cd1344297f70b9e39a1e4f467a4e1c10f1da54ff7a85c12197f6c652c8bdf", size = 56279010 }, - { url = "https://files.pythonhosted.org/packages/4a/aa/2c7ff0b5ee02eaef890c0ce7d4f74bc30901871c5e45dee1ae6d0083cd80/nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:99f1a32f1ac2bd134897fc7a203f779303261268a65762a623bf30cc9fe79117", size = 56279000 }, + { url = "https://files.pythonhosted.org/packages/45/5e/92aa15eca622a388b80fbf8375d4760738df6285b1e92c43d37390a33a9a/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_aarch64.whl", upload-time = 2025-03-07T01:46:10Z, size = 63625754, hashes = { sha256 = "dfab99248034673b779bc6decafdc3404a8a6f502462201f2f31f11354204acd" } }, + { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", upload-time = 2025-03-07T01:46:23Z, size = 63619976, hashes = { sha256 = "b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9" } }, + { url = "https://files.pythonhosted.org/packages/b9/75/70c05b2f3ed5be3bb30b7102b6eb78e100da4bbf6944fd6725c012831cab/nvidia_curand_cu12-10.3.9.90-py3-none-win_amd64.whl", upload-time = 2025-03-07T01:54:20Z, size = 62765309, hashes = { sha256 = "f149a8ca457277da854f89cf282d6ef43176861926c7ac85b2a0fbd237c587ec" } }, ] -[[package]] +[[packages]] name = "nvidia-cusolver-cu12" -version = "11.7.1.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-cublas-cu12" }, - { name = "nvidia-cusparse-cu12" }, - { name = "nvidia-nvjitlink-cu12" }, -] +version = "11.7.3.90" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/6e/c2cf12c9ff8b872e92b4a5740701e51ff17689c4d726fca91875b07f655d/nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9e49843a7707e42022babb9bcfa33c29857a93b88020c4e4434656a655b698c", size = 158229790 }, - { url = "https://files.pythonhosted.org/packages/9f/81/baba53585da791d043c10084cf9553e074548408e04ae884cfe9193bd484/nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6cf28f17f64107a0c4d7802be5ff5537b2130bfc112f25d5a30df227058ca0e6", size = 158229780 }, + { url = "https://files.pythonhosted.org/packages/c8/32/f7cd6ce8a7690544d084ea21c26e910a97e077c9b7f07bf5de623ee19981/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_aarch64.whl", upload-time = 2025-03-07T01:46:54Z, size = 267229841, hashes = { sha256 = "db9ed69dbef9715071232caa9b69c52ac7de3a95773c2db65bdba85916e4e5c0" } }, + { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", upload-time = 2025-03-07T01:47:16Z, size = 267506905, hashes = { sha256 = "4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450" } }, + { url = "https://files.pythonhosted.org/packages/13/c0/76ca8551b8a84146ffa189fec81c26d04adba4bc0dbe09cd6e6fd9b7de04/nvidia_cusolver_cu12-11.7.3.90-py3-none-win_amd64.whl", upload-time = 2025-03-07T01:54:39Z, size = 256720438, hashes = { sha256 = "4a550db115fcabc4d495eb7d39ac8b58d4ab5d8e63274d3754df1c0ad6a22d34" } }, ] -[[package]] +[[packages]] name = "nvidia-cusparse-cu12" -version = "12.5.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, -] +version = "12.5.8.93" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/06/1e/b8b7c2f4099a37b96af5c9bb158632ea9e5d9d27d7391d7eb8fc45236674/nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7556d9eca156e18184b94947ade0fba5bb47d69cec46bf8660fd2c71a4b48b73", size = 216561367 }, - { url = "https://files.pythonhosted.org/packages/43/ac/64c4316ba163e8217a99680c7605f779accffc6a4bcd0c778c12948d3707/nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:23749a6571191a215cb74d1cdbff4a86e7b19f1200c071b3fcf844a5bea23a2f", size = 216561357 }, + { url = "https://files.pythonhosted.org/packages/bc/f7/cd777c4109681367721b00a106f491e0d0d15cfa1fd59672ce580ce42a97/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-03-07T01:47:40Z, size = 288117129, hashes = { sha256 = "9b6c161cb130be1a07a27ea6923df8141f3c295852f4b260c65f18f3e0a091dc" } }, + { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-03-07T01:48:13Z, size = 288216466, hashes = { sha256 = "1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b" } }, + { url = "https://files.pythonhosted.org/packages/62/07/f3b2ad63f8e3d257a599f422ae34eb565e70c41031aecefa3d18b62cabd1/nvidia_cusparse_cu12-12.5.8.93-py3-none-win_amd64.whl", upload-time = 2025-03-07T01:55:07Z, size = 284937404, hashes = { sha256 = "9a33604331cb2cac199f2e7f5104dfbb8a5a898c367a53dfda9ff2acb6b6b4dd" } }, ] -[[package]] +[[packages]] name = "nvidia-cusparselt-cu12" -version = "0.6.3" -source = { registry = "https://pypi.org/simple" } +version = "0.7.1" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/9a/72ef35b399b0e183bc2e8f6f558036922d453c4d8237dab26c666a04244b/nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5c8a26c36445dd2e6812f1177978a24e2d37cacce7e090f297a688d1ec44f46", size = 156785796 }, + { url = "https://files.pythonhosted.org/packages/73/b9/598f6ff36faaece4b3c50d26f50e38661499ff34346f00e057760b35cc9d/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_aarch64.whl", upload-time = 2025-02-26T00:16:54Z, size = 283835557, hashes = { sha256 = "8878dce784d0fac90131b6817b607e803c36e629ba34dc5b433471382196b6a5" } }, + { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", upload-time = 2025-02-26T00:15:44Z, size = 287193691, hashes = { sha256 = "f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623" } }, + { url = "https://files.pythonhosted.org/packages/2f/d8/a6b0d0d0c2435e9310f3e2bb0d9c9dd4c33daef86aa5f30b3681defd37ea/nvidia_cusparselt_cu12-0.7.1-py3-none-win_amd64.whl", upload-time = 2025-02-26T00:14:47Z, size = 271020911, hashes = { sha256 = "f67fbb5831940ec829c9117b7f33807db9f9678dc2a617fbe781cac17b4e1075" } }, ] -[[package]] +[[packages]] name = "nvidia-nccl-cu12" -version = "2.26.2" -source = { registry = "https://pypi.org/simple" } +version = "2.27.5" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/67/ca/f42388aed0fddd64ade7493dbba36e1f534d4e6fdbdd355c6a90030ae028/nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:694cf3879a206553cc9d7dbda76b13efaf610fdb70a50cba303de1b0d1530ac6", size = 201319755 }, + { url = "https://files.pythonhosted.org/packages/bb/1c/857979db0ef194ca5e21478a0612bcdbbe59458d7694361882279947b349/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-06-26T04:11:04Z, size = 322400625, hashes = { sha256 = "31432ad4d1fb1004eb0c56203dc9bc2178a1ba69d1d9e02d64a6938ab5e40e7a" } }, + { url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-06-26T04:11:28Z, size = 322348229, hashes = { sha256 = "ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457" } }, ] -[[package]] +[[packages]] name = "nvidia-nvjitlink-cu12" -version = "12.6.85" -source = { registry = "https://pypi.org/simple" } +version = "12.8.93" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/d7/c5383e47c7e9bf1c99d5bd2a8c935af2b6d705ad831a7ec5c97db4d82f4f/nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:eedc36df9e88b682efe4309aa16b5b4e78c2407eac59e8c10a6a47535164369a", size = 19744971 }, + { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", upload-time = 2025-03-07T01:49:55Z, size = 39254836, hashes = { sha256 = "81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88" } }, + { url = "https://files.pythonhosted.org/packages/2a/a2/8cee5da30d13430e87bf99bb33455d2724d0a4a9cb5d7926d80ccb96d008/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-03-07T01:49:43Z, size = 38386204, hashes = { sha256 = "adccd7161ace7261e01bb91e44e88da350895c270d23f744f0820c818b7229e7" } }, + { url = "https://files.pythonhosted.org/packages/ed/d7/34f02dad2e30c31b10a51f6b04e025e5dd60e5f936af9045a9b858a05383/nvidia_nvjitlink_cu12-12.8.93-py3-none-win_amd64.whl", upload-time = 2025-03-07T01:56:24Z, size = 268553710, hashes = { sha256 = "bd93fbeeee850917903583587f4fc3a4eafa022e34572251368238ab5e6bd67f" } }, ] -[[package]] -name = "nvidia-nvtx-cu12" -version = "12.6.77" -source = { registry = "https://pypi.org/simple" } +[[packages]] +name = "nvidia-nvshmem-cu12" +version = "3.3.20" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/56/9a/fff8376f8e3d084cd1530e1ef7b879bb7d6d265620c95c1b322725c694f4/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b90bed3df379fa79afbd21be8e04a0314336b8ae16768b58f2d34cb1d04cd7d2", size = 89276 }, - { url = "https://files.pythonhosted.org/packages/9e/4e/0d0c945463719429b7bd21dece907ad0bde437a2ff12b9b12fee94722ab0/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6574241a3ec5fdc9334353ab8c479fe75841dbe8f4532a8fc97ce63503330ba1", size = 89265 }, + { url = "https://files.pythonhosted.org/packages/92/9d/3dd98852568fb845ec1f7902c90a22b240fe1cbabda411ccedf2fd737b7b/nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-08-04T20:24:59Z, size = 124484616, hashes = { sha256 = "0b0b960da3842212758e4fa4696b94f129090b30e5122fea3c5345916545cff0" } }, + { url = "https://files.pythonhosted.org/packages/3b/6c/99acb2f9eb85c29fc6f3a7ac4dccfd992e22666dd08a642b303311326a97/nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-08-04T20:25:19Z, size = 124657145, hashes = { sha256 = "d00f26d3f9b2e3c3065be895e3059d6479ea5c638a3f38c9fec49b1b9dd7c1e5" } }, ] -[[package]] -name = "openai" -version = "1.78.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/d1/7c/7c48bac9be52680e41e99ae7649d5da3a0184cd94081e028897f9005aa03/openai-1.78.0.tar.gz", hash = "sha256:254aef4980688468e96cbddb1f348ed01d274d02c64c6c69b0334bf001fb62b3", size = 442652 } +[[packages]] +name = "nvidia-nvtx-cu12" +version = "12.8.90" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/41/d64a6c56d0ec886b834caff7a07fc4d43e1987895594b144757e7a6b90d7/openai-1.78.0-py3-none-any.whl", hash = "sha256:1ade6a48cd323ad8a7715e7e1669bb97a17e1a5b8a916644261aaef4bf284778", size = 680407 }, + { url = "https://files.pythonhosted.org/packages/10/c0/1b303feea90d296f6176f32a2a70b5ef230f9bdeb3a72bddb0dc922dc137/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-03-07T01:42:23Z, size = 91161, hashes = { sha256 = "d7ad891da111ebafbf7e015d34879f7112832fc239ff0d7d776b6cb685274615" } }, + { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-03-07T01:42:44Z, size = 89954, hashes = { sha256 = "5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f" } }, + { url = "https://files.pythonhosted.org/packages/9f/99/4c9c0c329bf9fc125008c3b54c7c94c0023518d06fc025ae36431375e1fe/nvidia_nvtx_cu12-12.8.90-py3-none-win_amd64.whl", upload-time = 2025-03-07T01:52:24Z, size = 56492, hashes = { sha256 = "619c8304aedc69f02ea82dd244541a83c3d9d40993381b3b590f1adaed3db41e" } }, ] -[[package]] +[[packages]] +name = "openai" +version = "2.3.0" +sdist = { url = "https://files.pythonhosted.org/packages/de/90/8f26554d24d63ed4f94d33c24271559863223a67e624f4d2e65ba8e48dca/openai-2.3.0.tar.gz", upload-time = 2025-10-10T01:12:50Z, size = 589616, hashes = { sha256 = "8d213ee5aaf91737faea2d7fc1cd608657a5367a18966372a3756ceaabfbd812" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/9c/5b/4be258ff072ed8ee15f6bfd8d5a1a4618aa4704b127c0c5959212ad177d6/openai-2.3.0-py3-none-any.whl", upload-time = 2025-10-10T01:12:48Z, size = 999768, hashes = { sha256 = "a7aa83be6f7b0ab2e4d4d7bcaf36e3d790874c0167380c5d0afd0ed99a86bd7b" } }] + +[[packages]] name = "packaging" version = "25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, -] - -[[package]] +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", upload-time = 2025-04-19T11:48:59Z, size = 165727, hashes = { sha256 = "d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", upload-time = 2025-04-19T11:48:57Z, size = 66469, hashes = { sha256 = "29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484" } }] + +[[packages]] +name = "paginate" +version = "0.5.7" +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", upload-time = 2024-08-25T14:17:24Z, size = 19252, hashes = { sha256 = "22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", upload-time = 2024-08-25T14:17:22Z, size = 13746, hashes = { sha256 = "b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591" } }] + +[[packages]] +name = "pathspec" +version = "0.12.1" +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", upload-time = 2023-12-10T22:30:45Z, size = 51043, hashes = { sha256 = "a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", upload-time = 2023-12-10T22:30:43Z, size = 31191, hashes = { sha256 = "a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08" } }] + +[[packages]] name = "pillow" -version = "11.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098 }, - { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166 }, - { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674 }, - { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005 }, - { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707 }, - { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008 }, - { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420 }, - { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655 }, - { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329 }, - { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388 }, - { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950 }, - { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759 }, - { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284 }, - { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826 }, - { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329 }, - { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049 }, - { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408 }, - { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863 }, - { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938 }, - { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774 }, - { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895 }, - { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234 }, -] - -[[package]] +version = "12.0.0" +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", upload-time = 2025-10-15T18:24:14Z, size = 47008828, hashes = { sha256 = "87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/08/26e68b6b5da219c2a2cb7b563af008b53bb8e6b6fcb3fa40715fcdb2523a/pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", upload-time = 2025-10-15T18:21:27Z, size = 5289809, hashes = { sha256 = "3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b" } }, + { url = "https://files.pythonhosted.org/packages/cb/e9/4e58fb097fb74c7b4758a680aacd558810a417d1edaa7000142976ef9d2f/pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-10-15T18:21:29Z, size = 4650606, hashes = { sha256 = "1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1" } }, + { url = "https://files.pythonhosted.org/packages/4b/e0/1fa492aa9f77b3bc6d471c468e62bfea1823056bf7e5e4f1914d7ab2565e/pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-10-15T18:21:31Z, size = 6221023, hashes = { sha256 = "d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363" } }, + { url = "https://files.pythonhosted.org/packages/c1/09/4de7cd03e33734ccd0c876f0251401f1314e819cbfd89a0fcb6e77927cc6/pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-10-15T18:21:33Z, size = 8024937, hashes = { sha256 = "c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca" } }, + { url = "https://files.pythonhosted.org/packages/2e/69/0688e7c1390666592876d9d474f5e135abb4acb39dcb583c4dc5490f1aff/pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T18:21:35Z, size = 6334139, hashes = { sha256 = "d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e" } }, + { url = "https://files.pythonhosted.org/packages/ed/1c/880921e98f525b9b44ce747ad1ea8f73fd7e992bafe3ca5e5644bf433dea/pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T18:21:37Z, size = 7026074, hashes = { sha256 = "d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782" } }, + { url = "https://files.pythonhosted.org/packages/28/03/96f718331b19b355610ef4ebdbbde3557c726513030665071fd025745671/pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T18:21:39Z, size = 6448852, hashes = { sha256 = "32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10" } }, + { url = "https://files.pythonhosted.org/packages/3a/a0/6a193b3f0cc9437b122978d2c5cbce59510ccf9a5b48825096ed7472da2f/pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T18:21:40Z, size = 7117058, hashes = { sha256 = "c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa" } }, + { url = "https://files.pythonhosted.org/packages/a7/c4/043192375eaa4463254e8e61f0e2ec9a846b983929a8d0a7122e0a6d6fff/pillow-12.0.0-cp310-cp310-win32.whl", upload-time = 2025-10-15T18:21:42Z, size = 6295431, hashes = { sha256 = "bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275" } }, + { url = "https://files.pythonhosted.org/packages/92/c6/c2f2fc7e56301c21827e689bb8b0b465f1b52878b57471a070678c0c33cd/pillow-12.0.0-cp310-cp310-win_amd64.whl", upload-time = 2025-10-15T18:21:44Z, size = 7000412, hashes = { sha256 = "455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d" } }, + { url = "https://files.pythonhosted.org/packages/b2/d2/5f675067ba82da7a1c238a73b32e3fd78d67f9d9f80fbadd33a40b9c0481/pillow-12.0.0-cp310-cp310-win_arm64.whl", upload-time = 2025-10-15T18:21:46Z, size = 2435903, hashes = { sha256 = "6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7" } }, + { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", upload-time = 2025-10-15T18:21:47Z, size = 5289798, hashes = { sha256 = "0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc" } }, + { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-10-15T18:21:49Z, size = 4650589, hashes = { sha256 = "a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257" } }, + { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-10-15T18:21:51Z, size = 6230472, hashes = { sha256 = "110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642" } }, + { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-10-15T18:21:52Z, size = 8033887, hashes = { sha256 = "5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3" } }, + { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T18:21:54Z, size = 6343964, hashes = { sha256 = "aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c" } }, + { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T18:21:56Z, size = 7034756, hashes = { sha256 = "bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227" } }, + { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T18:21:57Z, size = 6458075, hashes = { sha256 = "90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b" } }, + { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T18:21:59Z, size = 7125955, hashes = { sha256 = "bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e" } }, + { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", upload-time = 2025-10-15T18:22:00Z, size = 6298440, hashes = { sha256 = "27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739" } }, + { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", upload-time = 2025-10-15T18:22:02Z, size = 6999256, hashes = { sha256 = "b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e" } }, + { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", upload-time = 2025-10-15T18:22:04Z, size = 2436025, hashes = { sha256 = "759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d" } }, + { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T18:22:05Z, size = 5249377, hashes = { sha256 = "53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371" } }, + { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-10-15T18:22:07Z, size = 4650343, hashes = { sha256 = "71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082" } }, + { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-10-15T18:22:09Z, size = 6232981, hashes = { sha256 = "2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f" } }, + { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-10-15T18:22:10Z, size = 8041399, hashes = { sha256 = "82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d" } }, + { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T18:22:12Z, size = 6347740, hashes = { sha256 = "55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953" } }, + { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T18:22:14Z, size = 7040201, hashes = { sha256 = "b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8" } }, + { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T18:22:16Z, size = 6462334, hashes = { sha256 = "c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79" } }, + { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T18:22:17Z, size = 7134162, hashes = { sha256 = "21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba" } }, + { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", upload-time = 2025-10-15T18:22:19Z, size = 6298769, hashes = { sha256 = "dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0" } }, + { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", upload-time = 2025-10-15T18:22:21Z, size = 7001107, hashes = { sha256 = "9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a" } }, + { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", upload-time = 2025-10-15T18:22:23Z, size = 2436012, hashes = { sha256 = "7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad" } }, + { url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", upload-time = 2025-10-15T18:22:25Z, size = 4045493, hashes = { sha256 = "0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643" } }, + { url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", upload-time = 2025-10-15T18:22:27Z, size = 4120461, hashes = { sha256 = "a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4" } }, + { url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", upload-time = 2025-10-15T18:22:28Z, size = 3576912, hashes = { sha256 = "1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399" } }, + { url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T18:22:30Z, size = 5249132, hashes = { sha256 = "c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5" } }, + { url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-10-15T18:22:32Z, size = 4650099, hashes = { sha256 = "5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b" } }, + { url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-10-15T18:22:34Z, size = 6230808, hashes = { sha256 = "bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3" } }, + { url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-10-15T18:22:36Z, size = 8037804, hashes = { sha256 = "a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07" } }, + { url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T18:22:38Z, size = 6345553, hashes = { sha256 = "0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e" } }, + { url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T18:22:39Z, size = 7037729, hashes = { sha256 = "f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344" } }, + { url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T18:22:41Z, size = 6459789, hashes = { sha256 = "6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27" } }, + { url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T18:22:43Z, size = 7130917, hashes = { sha256 = "d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79" } }, + { url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", upload-time = 2025-10-15T18:22:44Z, size = 6302391, hashes = { sha256 = "4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098" } }, + { url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", upload-time = 2025-10-15T18:22:46Z, size = 7007477, hashes = { sha256 = "4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905" } }, + { url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", upload-time = 2025-10-15T18:22:48Z, size = 2435918, hashes = { sha256 = "9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a" } }, + { url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T18:22:49Z, size = 5251406, hashes = { sha256 = "7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3" } }, + { url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-10-15T18:22:51Z, size = 4653218, hashes = { sha256 = "f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced" } }, + { url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-10-15T18:22:53Z, size = 6266564, hashes = { sha256 = "c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b" } }, + { url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-10-15T18:22:54Z, size = 8069260, hashes = { sha256 = "38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d" } }, + { url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T18:22:56Z, size = 6379248, hashes = { sha256 = "7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a" } }, + { url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T18:22:58Z, size = 7066043, hashes = { sha256 = "aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe" } }, + { url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T18:23:00Z, size = 6490915, hashes = { sha256 = "8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee" } }, + { url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T18:23:02Z, size = 7157998, hashes = { sha256 = "99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef" } }, + { url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", upload-time = 2025-10-15T18:23:04Z, size = 6306201, hashes = { sha256 = "4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9" } }, + { url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", upload-time = 2025-10-15T18:23:06Z, size = 7013165, hashes = { sha256 = "2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b" } }, + { url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", upload-time = 2025-10-15T18:23:08Z, size = 2437834, hashes = { sha256 = "26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47" } }, + { url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", upload-time = 2025-10-15T18:23:10Z, size = 4045531, hashes = { sha256 = "beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9" } }, + { url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", upload-time = 2025-10-15T18:23:12Z, size = 4120554, hashes = { sha256 = "d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2" } }, + { url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", upload-time = 2025-10-15T18:23:13Z, size = 3576812, hashes = { sha256 = "3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a" } }, + { url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", upload-time = 2025-10-15T18:23:15Z, size = 5252689, hashes = { sha256 = "e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b" } }, + { url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-10-15T18:23:17Z, size = 4650186, hashes = { sha256 = "266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad" } }, + { url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-10-15T18:23:18Z, size = 6230308, hashes = { sha256 = "58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01" } }, + { url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-10-15T18:23:20Z, size = 8039222, hashes = { sha256 = "f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c" } }, + { url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T18:23:23Z, size = 6346657, hashes = { sha256 = "6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e" } }, + { url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T18:23:25Z, size = 7038482, hashes = { sha256 = "c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e" } }, + { url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T18:23:27Z, size = 6461416, hashes = { sha256 = "fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9" } }, + { url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T18:23:29Z, size = 7131584, hashes = { sha256 = "d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab" } }, + { url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", upload-time = 2025-10-15T18:23:32Z, size = 6400621, hashes = { sha256 = "1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b" } }, + { url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", upload-time = 2025-10-15T18:23:34Z, size = 7142916, hashes = { sha256 = "8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b" } }, + { url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", upload-time = 2025-10-15T18:23:36Z, size = 2523836, hashes = { sha256 = "d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0" } }, + { url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", upload-time = 2025-10-15T18:23:38Z, size = 5255092, hashes = { sha256 = "1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6" } }, + { url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-10-15T18:23:40Z, size = 4653158, hashes = { sha256 = "b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6" } }, + { url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-10-15T18:23:42Z, size = 6267882, hashes = { sha256 = "fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1" } }, + { url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-10-15T18:23:44Z, size = 8071001, hashes = { sha256 = "7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e" } }, + { url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T18:23:46Z, size = 6380146, hashes = { sha256 = "5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca" } }, + { url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T18:23:47Z, size = 7067344, hashes = { sha256 = "d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925" } }, + { url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T18:23:49Z, size = 6491864, hashes = { sha256 = "792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8" } }, + { url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T18:23:51Z, size = 7158911, hashes = { sha256 = "afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4" } }, + { url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", upload-time = 2025-10-15T18:23:53Z, size = 6408045, hashes = { sha256 = "3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52" } }, + { url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", upload-time = 2025-10-15T18:23:55Z, size = 7148282, hashes = { sha256 = "905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a" } }, + { url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", upload-time = 2025-10-15T18:23:57Z, size = 2525630, hashes = { sha256 = "99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7" } }, + { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", upload-time = 2025-10-15T18:23:59Z, size = 5215068, hashes = { sha256 = "b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8" } }, + { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", upload-time = 2025-10-15T18:24:01Z, size = 4614994, hashes = { sha256 = "805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a" } }, + { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-10-15T18:24:03Z, size = 5246639, hashes = { sha256 = "cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197" } }, + { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-10-15T18:24:05Z, size = 6986839, hashes = { sha256 = "aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c" } }, + { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T18:24:07Z, size = 5313505, hashes = { sha256 = "09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e" } }, + { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T18:24:09Z, size = 5963654, hashes = { sha256 = "bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76" } }, + { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", upload-time = 2025-10-15T18:24:11Z, size = 6997850, hashes = { sha256 = "b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5" } }, +] + +[[packages]] +name = "platformdirs" +version = "4.5.0" +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", upload-time = 2025-10-08T17:44:48Z, size = 21632, hashes = { sha256 = "70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", upload-time = 2025-10-08T17:44:47Z, size = 18651, hashes = { sha256 = "e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3" } }] + +[[packages]] name = "pluggy" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, -] +version = "1.6.0" +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", upload-time = 2025-05-15T12:30:07Z, size = 69412, hashes = { sha256 = "7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", upload-time = 2025-05-15T12:30:06Z, size = 20538, hashes = { sha256 = "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" } }] -[[package]] +[[packages]] name = "prompt-toolkit" -version = "3.0.51" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 }, -] - -[[package]] +version = "3.0.52" +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", upload-time = 2025-08-27T15:24:02Z, size = 434198, hashes = { sha256 = "28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", upload-time = 2025-08-27T15:23:59Z, size = 391431, hashes = { sha256 = "9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955" } }] + +[[packages]] +name = "protobuf" +version = "6.33.0" +sdist = { url = "https://files.pythonhosted.org/packages/19/ff/64a6c8f420818bb873713988ca5492cba3a7946be57e027ac63495157d97/protobuf-6.33.0.tar.gz", upload-time = 2025-10-15T20:39:52Z, size = 443463, hashes = { sha256 = "140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/ee/52b3fa8feb6db4a833dfea4943e175ce645144532e8a90f72571ad85df4e/protobuf-6.33.0-cp310-abi3-win32.whl", upload-time = 2025-10-15T20:39:40Z, size = 425593, hashes = { sha256 = "d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035" } }, + { url = "https://files.pythonhosted.org/packages/7b/c6/7a465f1825872c55e0341ff4a80198743f73b69ce5d43ab18043699d1d81/protobuf-6.33.0-cp310-abi3-win_amd64.whl", upload-time = 2025-10-15T20:39:42Z, size = 436882, hashes = { sha256 = "9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee" } }, + { url = "https://files.pythonhosted.org/packages/e1/a9/b6eee662a6951b9c3640e8e452ab3e09f117d99fc10baa32d1581a0d4099/protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", upload-time = 2025-10-15T20:39:43Z, size = 427521, hashes = { sha256 = "905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455" } }, + { url = "https://files.pythonhosted.org/packages/10/35/16d31e0f92c6d2f0e77c2a3ba93185130ea13053dd16200a57434c882f2b/protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", upload-time = 2025-10-15T20:39:44Z, size = 324445, hashes = { sha256 = "e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90" } }, + { url = "https://files.pythonhosted.org/packages/e6/eb/2a981a13e35cda8b75b5585aaffae2eb904f8f351bdd3870769692acbd8a/protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", upload-time = 2025-10-15T20:39:46Z, size = 339159, hashes = { sha256 = "e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298" } }, + { url = "https://files.pythonhosted.org/packages/21/51/0b1cbad62074439b867b4e04cc09b93f6699d78fd191bed2bbb44562e077/protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", upload-time = 2025-10-15T20:39:47Z, size = 323172, hashes = { sha256 = "35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef" } }, + { url = "https://files.pythonhosted.org/packages/57/33/fbe61bbe91a656619f107b9dfd84b16e1438766bd62157b8d1c1214491fd/protobuf-6.33.0-cp39-cp39-win32.whl", upload-time = 2025-10-15T20:39:48Z, size = 425690, hashes = { sha256 = "cd33a8e38ea3e39df66e1bbc462b076d6e5ba3a4ebbde58219d777223a7873d3" } }, + { url = "https://files.pythonhosted.org/packages/2c/e4/ccc4814ad9d12fa404f7e5ce1983a2403644b0ed2588678c762b7a26ed92/protobuf-6.33.0-cp39-cp39-win_amd64.whl", upload-time = 2025-10-15T20:39:50Z, size = 436876, hashes = { sha256 = "c963e86c3655af3a917962c9619e1a6b9670540351d7af9439d06064e3317cc9" } }, + { url = "https://files.pythonhosted.org/packages/07/d1/0a28c21707807c6aacd5dc9c3704b2aa1effbf37adebd8caeaf68b17a636/protobuf-6.33.0-py3-none-any.whl", upload-time = 2025-10-15T20:39:51Z, size = 170477, hashes = { sha256 = "25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995" } }, +] + +[[packages]] name = "pycparser" -version = "2.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, -] +version = "2.23" +marker = "python_full_version >= '3.9' and implementation_name != 'PyPy' and platform_python_implementation != 'PyPy'" +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", upload-time = 2025-09-09T13:23:47Z, size = 173734, hashes = { sha256 = "78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", upload-time = 2025-09-09T13:23:46Z, size = 118140, hashes = { sha256 = "e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934" } }] -[[package]] +[[packages]] name = "pydantic" -version = "2.11.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/77/ab/5250d56ad03884ab5efd07f734203943c8a8ab40d551e208af81d0257bf2/pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d", size = 786540 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/12/46b65f3534d099349e38ef6ec98b1a5a81f42536d17e0ba382c28c67ba67/pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb", size = 443900 }, -] +version = "2.12.2" +sdist = { url = "https://files.pythonhosted.org/packages/8d/35/d319ed522433215526689bad428a94058b6dd12190ce7ddd78618ac14b28/pydantic-2.12.2.tar.gz", upload-time = 2025-10-14T15:02:21Z, size = 816358, hashes = { sha256 = "7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/6c/98/468cb649f208a6f1279448e6e5247b37ae79cf5e4041186f1e2ef3d16345/pydantic-2.12.2-py3-none-any.whl", upload-time = 2025-10-14T15:02:19Z, size = 460628, hashes = { sha256 = "25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae" } }] -[[package]] +[[packages]] name = "pydantic-core" -version = "2.33.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 }, - { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 }, - { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 }, - { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 }, - { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 }, - { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 }, - { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 }, - { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 }, - { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 }, - { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 }, - { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 }, - { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 }, - { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 }, -] - -[[package]] +version = "2.41.4" +sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", upload-time = 2025-10-14T10:23:47Z, size = 457557, hashes = { sha256 = "70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/3d/9b8ca77b0f76fcdbf8bc6b72474e264283f461284ca84ac3fde570c6c49a/pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T10:19:43Z, size = 2111197, hashes = { sha256 = "2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e" } }, + { url = "https://files.pythonhosted.org/packages/59/92/b7b0fe6ed4781642232755cb7e56a86e2041e1292f16d9ae410a0ccee5ac/pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-10-14T10:19:45Z, size = 1917909, hashes = { sha256 = "30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b" } }, + { url = "https://files.pythonhosted.org/packages/52/8c/3eb872009274ffa4fb6a9585114e161aa1a0915af2896e2d441642929fe4/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T10:19:46Z, size = 1969905, hashes = { sha256 = "d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd" } }, + { url = "https://files.pythonhosted.org/packages/f4/21/35adf4a753bcfaea22d925214a0c5b880792e3244731b3f3e6fec0d124f7/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T10:19:48Z, size = 2051938, hashes = { sha256 = "e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945" } }, + { url = "https://files.pythonhosted.org/packages/7d/d0/cdf7d126825e36d6e3f1eccf257da8954452934ede275a8f390eac775e89/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T10:19:49Z, size = 2250710, hashes = { sha256 = "1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706" } }, + { url = "https://files.pythonhosted.org/packages/2e/1c/af1e6fd5ea596327308f9c8d1654e1285cc3d8de0d584a3c9d7705bf8a7c/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T10:19:51Z, size = 2367445, hashes = { sha256 = "62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba" } }, + { url = "https://files.pythonhosted.org/packages/d3/81/8cece29a6ef1b3a92f956ea6da6250d5b2d2e7e4d513dd3b4f0c7a83dfea/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T10:19:52Z, size = 2072875, hashes = { sha256 = "2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b" } }, + { url = "https://files.pythonhosted.org/packages/e3/37/a6a579f5fc2cd4d5521284a0ab6a426cc6463a7b3897aeb95b12f1ba607b/pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-10-14T10:19:54Z, size = 2191329, hashes = { sha256 = "ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d" } }, + { url = "https://files.pythonhosted.org/packages/ae/03/505020dc5c54ec75ecba9f41119fd1e48f9e41e4629942494c4a8734ded1/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T10:19:55Z, size = 2151658, hashes = { sha256 = "e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700" } }, + { url = "https://files.pythonhosted.org/packages/cb/5d/2c0d09fb53aa03bbd2a214d89ebfa6304be7df9ed86ee3dc7770257f41ee/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", upload-time = 2025-10-14T10:19:57Z, size = 2316777, hashes = { sha256 = "1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6" } }, + { url = "https://files.pythonhosted.org/packages/ea/4b/c2c9c8f5e1f9c864b57d08539d9d3db160e00491c9f5ee90e1bfd905e644/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T10:19:59Z, size = 2320705, hashes = { sha256 = "a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9" } }, + { url = "https://files.pythonhosted.org/packages/28/c3/a74c1c37f49c0a02c89c7340fafc0ba816b29bd495d1a31ce1bdeacc6085/pydantic_core-2.41.4-cp310-cp310-win32.whl", upload-time = 2025-10-14T10:20:00Z, size = 1975464, hashes = { sha256 = "0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57" } }, + { url = "https://files.pythonhosted.org/packages/d6/23/5dd5c1324ba80303368f7569e2e2e1a721c7d9eb16acb7eb7b7f85cb1be2/pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", upload-time = 2025-10-14T10:20:03Z, size = 2024497, hashes = { sha256 = "a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc" } }, + { url = "https://files.pythonhosted.org/packages/62/4c/f6cbfa1e8efacd00b846764e8484fe173d25b8dab881e277a619177f3384/pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T10:20:04Z, size = 2109062, hashes = { sha256 = "28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80" } }, + { url = "https://files.pythonhosted.org/packages/21/f8/40b72d3868896bfcd410e1bd7e516e762d326201c48e5b4a06446f6cf9e8/pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-10-14T10:20:06Z, size = 1916301, hashes = { sha256 = "61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae" } }, + { url = "https://files.pythonhosted.org/packages/94/4d/d203dce8bee7faeca791671c88519969d98d3b4e8f225da5b96dad226fc8/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T10:20:08Z, size = 1968728, hashes = { sha256 = "eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827" } }, + { url = "https://files.pythonhosted.org/packages/65/f5/6a66187775df87c24d526985b3a5d78d861580ca466fbd9d4d0e792fcf6c/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T10:20:09Z, size = 2050238, hashes = { sha256 = "ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f" } }, + { url = "https://files.pythonhosted.org/packages/5e/b9/78336345de97298cf53236b2f271912ce11f32c1e59de25a374ce12f9cce/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T10:20:11Z, size = 2249424, hashes = { sha256 = "15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def" } }, + { url = "https://files.pythonhosted.org/packages/99/bb/a4584888b70ee594c3d374a71af5075a68654d6c780369df269118af7402/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T10:20:13Z, size = 2366047, hashes = { sha256 = "3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2" } }, + { url = "https://files.pythonhosted.org/packages/5f/8d/17fc5de9d6418e4d2ae8c675f905cdafdc59d3bf3bf9c946b7ab796a992a/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T10:20:15Z, size = 2071163, hashes = { sha256 = "6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8" } }, + { url = "https://files.pythonhosted.org/packages/54/e7/03d2c5c0b8ed37a4617430db68ec5e7dbba66358b629cd69e11b4d564367/pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-10-14T10:20:17Z, size = 2190585, hashes = { sha256 = "5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265" } }, + { url = "https://files.pythonhosted.org/packages/be/fc/15d1c9fe5ad9266a5897d9b932b7f53d7e5cfc800573917a2c5d6eea56ec/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T10:20:19Z, size = 2150109, hashes = { sha256 = "7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c" } }, + { url = "https://files.pythonhosted.org/packages/26/ef/e735dd008808226c83ba56972566138665b71477ad580fa5a21f0851df48/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", upload-time = 2025-10-14T10:20:20Z, size = 2315078, hashes = { sha256 = "37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a" } }, + { url = "https://files.pythonhosted.org/packages/90/00/806efdcf35ff2ac0f938362350cd9827b8afb116cc814b6b75cf23738c7c/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T10:20:22Z, size = 2318737, hashes = { sha256 = "0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e" } }, + { url = "https://files.pythonhosted.org/packages/41/7e/6ac90673fe6cb36621a2283552897838c020db343fa86e513d3f563b196f/pydantic_core-2.41.4-cp311-cp311-win32.whl", upload-time = 2025-10-14T10:20:23Z, size = 1974160, hashes = { sha256 = "09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03" } }, + { url = "https://files.pythonhosted.org/packages/e0/9d/7c5e24ee585c1f8b6356e1d11d40ab807ffde44d2db3b7dfd6d20b09720e/pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", upload-time = 2025-10-14T10:20:25Z, size = 2021883, hashes = { sha256 = "711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e" } }, + { url = "https://files.pythonhosted.org/packages/33/90/5c172357460fc28b2871eb4a0fb3843b136b429c6fa827e4b588877bf115/pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", upload-time = 2025-10-14T10:20:27Z, size = 1968026, hashes = { sha256 = "6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db" } }, + { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T10:20:28Z, size = 2099043, hashes = { sha256 = "ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887" } }, + { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-10-14T10:20:30Z, size = 1910699, hashes = { sha256 = "c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2" } }, + { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T10:20:32Z, size = 1952121, hashes = { sha256 = "304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999" } }, + { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T10:20:34Z, size = 2041590, hashes = { sha256 = "025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4" } }, + { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T10:20:35Z, size = 2219869, hashes = { sha256 = "b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f" } }, + { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T10:20:37Z, size = 2345169, hashes = { sha256 = "dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b" } }, + { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T10:20:39Z, size = 2070165, hashes = { sha256 = "98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47" } }, + { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-10-14T10:20:41Z, size = 2189067, hashes = { sha256 = "ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970" } }, + { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T10:20:43Z, size = 2132997, hashes = { sha256 = "3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed" } }, + { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", upload-time = 2025-10-14T10:20:44Z, size = 2307187, hashes = { sha256 = "f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8" } }, + { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T10:20:46Z, size = 2305204, hashes = { sha256 = "84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431" } }, + { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", upload-time = 2025-10-14T10:20:48Z, size = 1972536, hashes = { sha256 = "9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd" } }, + { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", upload-time = 2025-10-14T10:20:50Z, size = 2031132, hashes = { sha256 = "d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff" } }, + { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", upload-time = 2025-10-14T10:20:52Z, size = 1969483, hashes = { sha256 = "833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8" } }, + { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T10:20:54Z, size = 2105688, hashes = { sha256 = "85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746" } }, + { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-10-14T10:20:56Z, size = 1910807, hashes = { sha256 = "e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced" } }, + { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T10:20:57Z, size = 1956669, hashes = { sha256 = "94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a" } }, + { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T10:21:00Z, size = 2051629, hashes = { sha256 = "de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02" } }, + { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T10:21:01Z, size = 2224049, hashes = { sha256 = "664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1" } }, + { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T10:21:03Z, size = 2342409, hashes = { sha256 = "d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2" } }, + { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T10:21:05Z, size = 2069635, hashes = { sha256 = "a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84" } }, + { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-10-14T10:21:07Z, size = 2194284, hashes = { sha256 = "1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d" } }, + { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T10:21:08Z, size = 2137566, hashes = { sha256 = "7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d" } }, + { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", upload-time = 2025-10-14T10:21:10Z, size = 2316809, hashes = { sha256 = "285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2" } }, + { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T10:21:12Z, size = 2311119, hashes = { sha256 = "f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab" } }, + { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", upload-time = 2025-10-14T10:21:14Z, size = 1981398, hashes = { sha256 = "ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c" } }, + { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", upload-time = 2025-10-14T10:21:16Z, size = 2030735, hashes = { sha256 = "d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4" } }, + { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", upload-time = 2025-10-14T10:21:18Z, size = 1973209, hashes = { sha256 = "f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564" } }, + { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-10-14T10:21:20Z, size = 1877324, hashes = { sha256 = "ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4" } }, + { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T10:21:22Z, size = 1884515, hashes = { sha256 = "a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2" } }, + { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T10:21:26Z, size = 2042819, hashes = { sha256 = "d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf" } }, + { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", upload-time = 2025-10-14T10:21:28Z, size = 1995866, hashes = { sha256 = "d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2" } }, + { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", upload-time = 2025-10-14T10:21:30Z, size = 1970034, hashes = { sha256 = "19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89" } }, + { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T10:21:32Z, size = 2102022, hashes = { sha256 = "e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1" } }, + { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-10-14T10:21:34Z, size = 1905495, hashes = { sha256 = "82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac" } }, + { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T10:21:36Z, size = 1956131, hashes = { sha256 = "fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554" } }, + { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T10:21:38Z, size = 2052236, hashes = { sha256 = "b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e" } }, + { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T10:21:41Z, size = 2223573, hashes = { sha256 = "e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616" } }, + { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T10:21:44Z, size = 2342467, hashes = { sha256 = "31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af" } }, + { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T10:21:46Z, size = 2063754, hashes = { sha256 = "a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12" } }, + { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-10-14T10:21:48Z, size = 2196754, hashes = { sha256 = "d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d" } }, + { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T10:21:50Z, size = 2137115, hashes = { sha256 = "c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad" } }, + { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", upload-time = 2025-10-14T10:21:52Z, size = 2317400, hashes = { sha256 = "b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a" } }, + { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T10:21:55Z, size = 2312070, hashes = { sha256 = "6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025" } }, + { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", upload-time = 2025-10-14T10:21:57Z, size = 1982277, hashes = { sha256 = "5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e" } }, + { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", upload-time = 2025-10-14T10:21:59Z, size = 2024608, hashes = { sha256 = "557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894" } }, + { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", upload-time = 2025-10-14T10:22:01Z, size = 1967614, hashes = { sha256 = "3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d" } }, + { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-10-14T10:22:04Z, size = 1876904, hashes = { sha256 = "6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da" } }, + { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T10:22:06Z, size = 1882538, hashes = { sha256 = "523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e" } }, + { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T10:22:08Z, size = 2041183, hashes = { sha256 = "5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa" } }, + { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", upload-time = 2025-10-14T10:22:11Z, size = 1993542, hashes = { sha256 = "de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d" } }, + { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", upload-time = 2025-10-14T10:22:13Z, size = 1973897, hashes = { sha256 = "d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0" } }, + { url = "https://files.pythonhosted.org/packages/2c/36/f86d582be5fb47d4014506cd9ddd10a3979b6d0f2d237aa6ad3e7033b3ea/pydantic_core-2.41.4-cp39-cp39-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T10:22:16Z, size = 2112444, hashes = { sha256 = "646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062" } }, + { url = "https://files.pythonhosted.org/packages/ba/e5/63c521dc2dd106ba6b5941c080617ea9db252f8a7d5625231e9d761bc28c/pydantic_core-2.41.4-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2025-10-14T10:22:19Z, size = 1938218, hashes = { sha256 = "cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338" } }, + { url = "https://files.pythonhosted.org/packages/30/56/c84b638a3e6e9f5a612b9f5abdad73182520423de43669d639ed4f14b011/pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T10:22:21Z, size = 1971449, hashes = { sha256 = "692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d" } }, + { url = "https://files.pythonhosted.org/packages/99/c6/e974aade34fc7a0248fdfd0a373d62693502a407c596ab3470165e38183c/pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T10:22:24Z, size = 2054023, hashes = { sha256 = "d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7" } }, + { url = "https://files.pythonhosted.org/packages/4f/91/2507dda801f50980a38d1353c313e8f51349a42b008e63a4e45bf4620562/pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T10:22:26Z, size = 2251614, hashes = { sha256 = "e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166" } }, + { url = "https://files.pythonhosted.org/packages/b2/ad/05d886bc96938f4d31bed24e8d3fc3496d9aea7e77bcff6e4b93127c6de7/pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T10:22:28Z, size = 2378807, hashes = { sha256 = "df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e" } }, + { url = "https://files.pythonhosted.org/packages/6a/0a/d26e1bb9a80b9fc12cc30d9288193fbc9e60a799e55843804ee37bd38a9c/pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T10:22:30Z, size = 2076891, hashes = { sha256 = "66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891" } }, + { url = "https://files.pythonhosted.org/packages/d9/66/af014e3a294d9933ebfecf11a5d858709014bd2315fa9616195374dd82f0/pydantic_core-2.41.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-10-14T10:22:33Z, size = 2192179, hashes = { sha256 = "fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb" } }, + { url = "https://files.pythonhosted.org/packages/e7/3e/79783f97024037d0ea6e1b3ebcd761463a925199e04ce2625727e9f27d06/pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T10:22:35Z, size = 2153067, hashes = { sha256 = "6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514" } }, + { url = "https://files.pythonhosted.org/packages/b3/97/ea83b0f87d9e742405fb687d5682e7a26334eef2c82a2de06bfbdc305fab/pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_armv7l.whl", upload-time = 2025-10-14T10:22:38Z, size = 2319048, hashes = { sha256 = "44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005" } }, + { url = "https://files.pythonhosted.org/packages/64/4a/36d8c966a0b086362ac10a7ee75978ed15c5f2dfdfc02a1578d19d3802fb/pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T10:22:40Z, size = 2321830, hashes = { sha256 = "170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8" } }, + { url = "https://files.pythonhosted.org/packages/a2/6e/d80cc4909dde5f6842861288aa1a7181e7afbfc50940c862ed2848df15bd/pydantic_core-2.41.4-cp39-cp39-win32.whl", upload-time = 2025-10-14T10:22:42Z, size = 1976706, hashes = { sha256 = "3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb" } }, + { url = "https://files.pythonhosted.org/packages/29/ee/5bda8d960d4a8b24a7eeb8a856efa9c865a7a6cab714ed387b29507dc278/pydantic_core-2.41.4-cp39-cp39-win_amd64.whl", upload-time = 2025-10-14T10:22:44Z, size = 2027640, hashes = { sha256 = "a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332" } }, + { url = "https://files.pythonhosted.org/packages/b0/12/5ba58daa7f453454464f92b3ca7b9d7c657d8641c48e370c3ebc9a82dd78/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T10:22:47Z, size = 2122139, hashes = { sha256 = "a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b" } }, + { url = "https://files.pythonhosted.org/packages/21/fb/6860126a77725c3108baecd10fd3d75fec25191d6381b6eb2ac660228eac/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", upload-time = 2025-10-14T10:22:49Z, size = 1936674, hashes = { sha256 = "d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42" } }, + { url = "https://files.pythonhosted.org/packages/de/be/57dcaa3ed595d81f8757e2b44a38240ac5d37628bce25fb20d02c7018776/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T10:22:52Z, size = 1956398, hashes = { sha256 = "0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee" } }, + { url = "https://files.pythonhosted.org/packages/2f/1d/679a344fadb9695f1a6a294d739fbd21d71fa023286daeea8c0ed49e7c2b/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T10:22:54Z, size = 2138674, hashes = { sha256 = "1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c" } }, + { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T10:22:56Z, size = 2112087, hashes = { sha256 = "4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537" } }, + { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", upload-time = 2025-10-14T10:22:59Z, size = 1920387, hashes = { sha256 = "4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94" } }, + { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T10:23:02Z, size = 1951495, hashes = { sha256 = "e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c" } }, + { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T10:23:04Z, size = 2139008, hashes = { sha256 = "bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335" } }, + { url = "https://files.pythonhosted.org/packages/5d/d4/912e976a2dd0b49f31c98a060ca90b353f3b73ee3ea2fd0030412f6ac5ec/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T10:23:06Z, size = 2106739, hashes = { sha256 = "1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00" } }, + { url = "https://files.pythonhosted.org/packages/71/f0/66ec5a626c81eba326072d6ee2b127f8c139543f1bf609b4842978d37833/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", upload-time = 2025-10-14T10:23:09Z, size = 1932549, hashes = { sha256 = "3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9" } }, + { url = "https://files.pythonhosted.org/packages/c4/af/625626278ca801ea0a658c2dcf290dc9f21bb383098e99e7c6a029fccfc0/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T10:23:11Z, size = 2135093, hashes = { sha256 = "7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2" } }, + { url = "https://files.pythonhosted.org/packages/20/f6/2fba049f54e0f4975fef66be654c597a1d005320fa141863699180c7697d/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-10-14T10:23:14Z, size = 2187971, hashes = { sha256 = "b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258" } }, + { url = "https://files.pythonhosted.org/packages/0e/80/65ab839a2dfcd3b949202f9d920c34f9de5a537c3646662bdf2f7d999680/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T10:23:16Z, size = 2147939, hashes = { sha256 = "6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347" } }, + { url = "https://files.pythonhosted.org/packages/44/58/627565d3d182ce6dfda18b8e1c841eede3629d59c9d7cbc1e12a03aeb328/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", upload-time = 2025-10-14T10:23:19Z, size = 2311400, hashes = { sha256 = "4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa" } }, + { url = "https://files.pythonhosted.org/packages/24/06/8a84711162ad5a5f19a88cead37cca81b4b1f294f46260ef7334ae4f24d3/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T10:23:21Z, size = 2316840, hashes = { sha256 = "b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a" } }, + { url = "https://files.pythonhosted.org/packages/aa/8b/b7bb512a4682a2f7fbfae152a755d37351743900226d29bd953aaf870eaa/pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", upload-time = 2025-10-14T10:23:24Z, size = 2149135, hashes = { sha256 = "3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d" } }, + { url = "https://files.pythonhosted.org/packages/7e/7d/138e902ed6399b866f7cfe4435d22445e16fff888a1c00560d9dc79a780f/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T10:23:26Z, size = 2104721, hashes = { sha256 = "491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5" } }, + { url = "https://files.pythonhosted.org/packages/47/13/0525623cf94627f7b53b4c2034c81edc8491cbfc7c28d5447fa318791479/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", upload-time = 2025-10-14T10:23:29Z, size = 1931608, hashes = { sha256 = "54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2" } }, + { url = "https://files.pythonhosted.org/packages/d6/f9/744bc98137d6ef0a233f808bfc9b18cf94624bf30836a18d3b05d08bf418/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T10:23:32Z, size = 2132986, hashes = { sha256 = "eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd" } }, + { url = "https://files.pythonhosted.org/packages/17/c8/629e88920171173f6049386cc71f893dff03209a9ef32b4d2f7e7c264bcf/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-10-14T10:23:34Z, size = 2187516, hashes = { sha256 = "6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c" } }, + { url = "https://files.pythonhosted.org/packages/2e/0f/4f2734688d98488782218ca61bcc118329bf5de05bb7fe3adc7dd79b0b86/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T10:23:37Z, size = 2146146, hashes = { sha256 = "26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405" } }, + { url = "https://files.pythonhosted.org/packages/ed/f2/ab385dbd94a052c62224b99cf99002eee99dbec40e10006c78575aead256/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", upload-time = 2025-10-14T10:23:40Z, size = 2311296, hashes = { sha256 = "ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8" } }, + { url = "https://files.pythonhosted.org/packages/fc/8e/e4f12afe1beeb9823bba5375f8f258df0cc61b056b0195fb1cf9f62a1a58/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T10:23:42Z, size = 2315386, hashes = { sha256 = "5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308" } }, + { url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", upload-time = 2025-10-14T10:23:45Z, size = 2147775, hashes = { sha256 = "c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f" } }, +] + +[[packages]] name = "pydantic-settings" -version = "2.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356 }, -] - -[[package]] -name = "pyjwt" -version = "2.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344 }, -] - -[[package]] +version = "2.11.0" +sdist = { url = "https://files.pythonhosted.org/packages/20/c5/dbbc27b814c71676593d1c3f718e6cd7d4f00652cefa24b75f7aa3efb25e/pydantic_settings-2.11.0.tar.gz", upload-time = 2025-09-24T14:19:11Z, size = 188394, hashes = { sha256 = "d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", upload-time = 2025-09-24T14:19:10Z, size = 48608, hashes = { sha256 = "fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c" } }] + +[[packages]] +name = "pygments" +version = "2.19.2" +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", upload-time = 2025-06-21T13:39:12Z, size = 4968631, hashes = { sha256 = "636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", upload-time = 2025-06-21T13:39:07Z, size = 1225217, hashes = { sha256 = "86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" } }] + +[[packages]] +name = "pymdown-extensions" +version = "10.16.1" +sdist = { url = "https://files.pythonhosted.org/packages/55/b3/6d2b3f149bc5413b0a29761c2c5832d8ce904a1d7f621e86616d96f505cc/pymdown_extensions-10.16.1.tar.gz", upload-time = 2025-07-28T16:19:34Z, size = 853277, hashes = { sha256 = "aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl", upload-time = 2025-07-28T16:19:31Z, size = 266178, hashes = { sha256 = "d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d" } }] + +[[packages]] name = "pymgclient" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "networkx" }, - { name = "pyopenssl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6e/58/5da9c14122ce01cd428f729206c79458f7f6d7ff997a80938fa4c9b63ea3/pymgclient-1.4.0.tar.gz", hash = "sha256:6dfe026ffbfa9a81ad9b042589a536a3fab8bcb630b6c44f3d33ef97ed1bd1b4", size = 127094 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/3e/ea749505e1b32350158ebd3a4b8b3f565426c833ce52adffd4149e0c902d/pymgclient-1.4.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4be6217832b778d7d9a20391de2c96259cac5ca152844191464a0be9b2e9630c", size = 2458110 }, - { url = "https://files.pythonhosted.org/packages/45/a7/4269f217744924633cb6624a2855c6fe9e8cfcca243d0d17a2eb41a76029/pymgclient-1.4.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:a87eec1fde175924a061d0f3ea8e4acedad444b33b82b1da0b7b320fc46e91e3", size = 2455399 }, - { url = "https://files.pythonhosted.org/packages/66/e3/15f242f8515ff4485dc6d19c6991f462d48838535e535e5f70c446f8c51c/pymgclient-1.4.0-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:3a0c7fa17dc046a82c3f48125cde82fdf20a3803cf361774fdac709072ed4201", size = 2461777 }, - { url = "https://files.pythonhosted.org/packages/70/08/0ba18b451b70fe76bf691b16f7db1dcce3cd2edd9d3bee941e688bb7c882/pymgclient-1.4.0-cp313-cp313-manylinux_2_38_x86_64.manylinux_2_39_x86_64.whl", hash = "sha256:bf701b47a1d0c1eee2a8a16bde0d96a26669d2ef652ea1a32f9c1d1a6e8b5393", size = 2369362 }, - { url = "https://files.pythonhosted.org/packages/bb/f4/098b11a1ab25498161a09f5e44fbc43ba8ec41ba5df2b100422627d247db/pymgclient-1.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3ee8d8205537291b8d5e6193823bdd48d2db9992075b0565f7c4f789146624a5", size = 2203872 }, -] - -[[package]] +version = "1.5.1" +sdist = { url = "https://files.pythonhosted.org/packages/d2/78/15d68f3389b4a28f61fe385a499d71ac00019b449785d937f649e20e2a46/pymgclient-1.5.1.tar.gz", upload-time = 2025-08-20T13:51:43Z, size = 129855, hashes = { sha256 = "defce8fc712ed2ca32ca75acfb3787b43a89592d8cef10b0f3e2aa7092e3d140" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/51/20b8b6cf1a04dd6a4be3d842039619c60d7c125ba9a3a47dfb67686117f2/pymgclient-1.5.1-cp310-cp310-macosx_14_0_arm64.whl", upload-time = 2025-08-20T13:51:20Z, size = 2463744, hashes = { sha256 = "36fc48656c3b28887495f7f3301fba5791a0ffa505caf07ee225f147c3e28162" } }, + { url = "https://files.pythonhosted.org/packages/53/05/e6547655f01484305fbe79f080a66acdb770e80eb18799c3bfc9af58dd96/pymgclient-1.5.1-cp310-cp310-macosx_15_0_arm64.whl", upload-time = 2025-08-20T13:51:22Z, size = 2471065, hashes = { sha256 = "db43fbe52ed283f1d7f5db4687f29b6a0481b0fb3057d7da05104a9c669569c3" } }, + { url = "https://files.pythonhosted.org/packages/67/0b/63f6fe96ffde22449a600e8ab703aa25b289645d6d9f3569da761a6ac9e2/pymgclient-1.5.1-cp310-cp310-manylinux_2_38_x86_64.manylinux_2_39_x86_64.whl", upload-time = 2025-08-20T13:51:24Z, size = 2367184, hashes = { sha256 = "9d9b13774e996544cdae12757d7ae5530d78a112fd4b62cd583a22a508786b45" } }, + { url = "https://files.pythonhosted.org/packages/ad/aa/4ed8b48189e2c7898b6858ad0aa4fab22cbc8eb1e9d9e97e2aa6a91f7830/pymgclient-1.5.1-cp310-cp310-win_amd64.whl", upload-time = 2025-08-20T13:51:26Z, size = 2345604, hashes = { sha256 = "53e9b6cd3742d4c9ebf25fe6fb646c8ca96f7b8836a5f6fdb7b8614624b81b3a" } }, + { url = "https://files.pythonhosted.org/packages/57/20/010d02afc24e445647ca511259b58c1c1e80b5f2ad1d5e48f5694d818919/pymgclient-1.5.1-cp311-cp311-macosx_14_0_arm64.whl", upload-time = 2025-08-20T13:51:27Z, size = 2463638, hashes = { sha256 = "f953f690fa4d6343bba31f192b6740fb8e4dd570e4d6c08b51abfcf528a991fc" } }, + { url = "https://files.pythonhosted.org/packages/c8/83/0a3e70fcbd7f5dcafdcd095ab1a00d45172d89a00a4aac76d56a9326ffaa/pymgclient-1.5.1-cp311-cp311-macosx_15_0_arm64.whl", upload-time = 2025-08-20T13:51:29Z, size = 2471060, hashes = { sha256 = "f7463d60e2c4ce1492be8bf2a7e9cc89759b64e5ba98d9efa9e58b47c127aebd" } }, + { url = "https://files.pythonhosted.org/packages/53/75/984b8b02e02861d57dcaf5cc766611f4848356026305655cd110cd6b8bd7/pymgclient-1.5.1-cp311-cp311-manylinux_2_38_x86_64.manylinux_2_39_x86_64.whl", upload-time = 2025-08-20T13:51:30Z, size = 2370255, hashes = { sha256 = "a95df69dfe5eac4c66ea5974f401ea9cf635de7adb04c0a97ec8c1cb3909e615" } }, + { url = "https://files.pythonhosted.org/packages/61/f6/ddc2be9ac5daa5654c3d13eaa0321392fe39986a79796e281fb70912ef9b/pymgclient-1.5.1-cp311-cp311-win_amd64.whl", upload-time = 2025-08-20T13:51:31Z, size = 2345614, hashes = { sha256 = "ce5cb8fc6de966cdf7d7c0a58a742321f50cda6ff84ec20ad8e6123bb9344494" } }, + { url = "https://files.pythonhosted.org/packages/d5/27/c1045f7d32e20e7b29a522e8c1458fc8c1bcc84c799e9804ea342915a8da/pymgclient-1.5.1-cp312-cp312-macosx_14_0_arm64.whl", upload-time = 2025-08-20T13:51:33Z, size = 2463072, hashes = { sha256 = "504d0c3c3417d029a6376b810c66aa8b2299357c067d0f6292a78b1e1c5f89df" } }, + { url = "https://files.pythonhosted.org/packages/fa/41/9d4a67cc207300f6bf454948cc7ca6da6dc1ef9717ac4c7a0fc83d913d5b/pymgclient-1.5.1-cp312-cp312-macosx_15_0_arm64.whl", upload-time = 2025-08-20T13:51:34Z, size = 2470601, hashes = { sha256 = "e543f9328f45254bdbc497a0afb03f75be25641595ef38faa27931a1bb143ac6" } }, + { url = "https://files.pythonhosted.org/packages/6d/9f/89ab956fc0ef084242bda5a62a2e9d213a8af6ef27d353d7c6a31e26429c/pymgclient-1.5.1-cp312-cp312-manylinux_2_38_x86_64.manylinux_2_39_x86_64.whl", upload-time = 2025-08-20T13:51:36Z, size = 2374887, hashes = { sha256 = "21af7e4eac4d3d6a68dc9fee9d9598c13d9700842ebb9c51c65d67add8bbfba1" } }, + { url = "https://files.pythonhosted.org/packages/1b/68/cbd7eefa98f6a46a3a8d27009530d431f005c90c74eca205b6cb71be33a4/pymgclient-1.5.1-cp312-cp312-win_amd64.whl", upload-time = 2025-08-20T13:51:37Z, size = 2346293, hashes = { sha256 = "729c4a025c2eda7673bd7eeb60c3273194b63acbf0397ebbf7f33c272937ba8d" } }, + { url = "https://files.pythonhosted.org/packages/33/33/d5b68864a5f19a36eadfe195884202e547bbc858f76beb92aa761d8148b0/pymgclient-1.5.1-cp313-cp313-macosx_14_0_arm64.whl", upload-time = 2025-08-20T13:51:38Z, size = 2463067, hashes = { sha256 = "7ca3ca6a00c57638f141a20990fedf26f3558903ce92c8f9c1b5e3709280699f" } }, + { url = "https://files.pythonhosted.org/packages/42/58/7b40481fe99a4f187ea3eda9ad325b4c2a35c3fbfad15563f469b801f5fe/pymgclient-1.5.1-cp313-cp313-macosx_15_0_arm64.whl", upload-time = 2025-08-20T13:51:40Z, size = 2470605, hashes = { sha256 = "cde56666e314406b7eace394b3f9cd067e15f73f0d55e1a3892ab02f8985ede2" } }, + { url = "https://files.pythonhosted.org/packages/76/c9/5f1dd1af7d4c7e5897619ffd4e190deb0d866de567b4cd0f2ea00df7bdb9/pymgclient-1.5.1-cp313-cp313-manylinux_2_38_x86_64.manylinux_2_39_x86_64.whl", upload-time = 2025-08-20T13:51:41Z, size = 2374943, hashes = { sha256 = "3b8b6c36a74970ba2e93b8329fddf9728c562c1a6f3df846ffbc833eea525052" } }, + { url = "https://files.pythonhosted.org/packages/f6/4d/d02cd10cdfc907c74441081896c52dec65c50310d001c26394195efec535/pymgclient-1.5.1-cp313-cp313-win_amd64.whl", upload-time = 2025-08-20T13:51:42Z, size = 2346729, hashes = { sha256 = "b8330095b124a783d2de7a57913e25928737cdf88f28fd72a5682abfd49e8eb4" } }, +] + +[[packages]] name = "pyopenssl" -version = "25.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/26/e25b4a374b4639e0c235527bbe31c0524f26eda701d79456a7e1877f4cc5/pyopenssl-25.0.0.tar.gz", hash = "sha256:cd2cef799efa3936bb08e8ccb9433a575722b9dd986023f1cabc4ae64e9dac16", size = 179573 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/d7/eb76863d2060dcbe7c7e6cccfd95ac02ea0b9acc37745a0d99ff6457aefb/pyOpenSSL-25.0.0-py3-none-any.whl", hash = "sha256:424c247065e46e76a37411b9ab1782541c23bb658bf003772c3405fbaa128e90", size = 56453 }, -] +version = "25.3.0" +sdist = { url = "https://files.pythonhosted.org/packages/80/be/97b83a464498a79103036bc74d1038df4a7ef0e402cfaf4d5e113fb14759/pyopenssl-25.3.0.tar.gz", upload-time = 2025-09-17T00:32:21Z, size = 184073, hashes = { sha256 = "c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/d1/81/ef2b1dfd1862567d573a4fdbc9f969067621764fbb74338496840a1d2977/pyopenssl-25.3.0-py3-none-any.whl", upload-time = 2025-09-17T00:32:19Z, size = 57268, hashes = { sha256 = "1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6" } }] -[[package]] +[[packages]] +name = "pyright" +version = "1.1.406" +sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", upload-time = 2025-10-02T01:04:45Z, size = 4113151, hashes = { sha256 = "c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", upload-time = 2025-10-02T01:04:43Z, size = 5980982, hashes = { sha256 = "1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71" } }] + +[[packages]] name = "pytest" -version = "8.3.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, -] +version = "8.4.2" +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", upload-time = 2025-09-04T14:34:22Z, size = 1519618, hashes = { sha256 = "86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", upload-time = 2025-09-04T14:34:20Z, size = 365750, hashes = { sha256 = "872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79" } }] + +[[packages]] +name = "pytest-cov" +version = "7.0.0" +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", upload-time = 2025-09-09T10:57:02Z, size = 54328, hashes = { sha256 = "33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", upload-time = 2025-09-09T10:57:00Z, size = 22424, hashes = { sha256 = "3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861" } }] -[[package]] +[[packages]] 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 } -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 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", upload-time = 2024-03-01T18:36:20Z, size = 342432, hashes = { sha256 = "37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", upload-time = 2024-03-01T18:36:18Z, size = 229892, hashes = { sha256 = "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" } }] -[[package]] +[[packages]] name = "python-dotenv" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, -] +version = "1.1.1" +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", upload-time = 2025-06-24T04:21:07Z, size = 41978, hashes = { sha256 = "a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", upload-time = 2025-06-24T04:21:06Z, size = 20556, hashes = { sha256 = "31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc" } }] -[[package]] -name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, -] +[[packages]] +name = "pytz" +version = "2025.2" +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", upload-time = 2025-03-25T02:25:00Z, size = 320884, hashes = { sha256 = "360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", upload-time = 2025-03-25T02:24:58Z, size = 509225, hashes = { sha256 = "5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00" } }] -[[package]] +[[packages]] +name = "pyyaml" +version = "6.0.3" +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", upload-time = 2025-09-25T21:33:16Z, size = 130960, hashes = { sha256 = "d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/a2/09f67a3589cb4320fb5ce90d3fd4c9752636b8b6ad8f34b54d76c5a54693/PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", upload-time = 2025-09-29T20:27:35Z, size = 186824, hashes = { sha256 = "c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f" } }, + { url = "https://files.pythonhosted.org/packages/02/72/d972384252432d57f248767556ac083793292a4adf4e2d85dfe785ec2659/PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-29T20:27:38Z, size = 795069, hashes = { sha256 = "9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4" } }, + { url = "https://files.pythonhosted.org/packages/a7/3b/6c58ac0fa7c4e1b35e48024eb03d00817438310447f93ef4431673c24138/PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-29T20:27:39Z, size = 862585, hashes = { sha256 = "efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3" } }, + { url = "https://files.pythonhosted.org/packages/25/a2/b725b61ac76a75583ae7104b3209f75ea44b13cfd026aa535ece22b7f22e/PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-29T20:27:41Z, size = 806018, hashes = { sha256 = "22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6" } }, + { url = "https://files.pythonhosted.org/packages/6f/b0/b2227677b2d1036d84f5ee95eb948e7af53d59fe3e4328784e4d290607e0/PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", upload-time = 2025-09-29T20:27:42Z, size = 802822, hashes = { sha256 = "6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369" } }, + { url = "https://files.pythonhosted.org/packages/99/a5/718a8ea22521e06ef19f91945766a892c5ceb1855df6adbde67d997ea7ed/PyYAML-6.0.3-cp38-cp38-win32.whl", upload-time = 2025-09-29T20:27:44Z, size = 143744, hashes = { sha256 = "3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295" } }, + { url = "https://files.pythonhosted.org/packages/76/b2/2b69cee94c9eb215216fc05778675c393e3aa541131dc910df8e52c83776/PyYAML-6.0.3-cp38-cp38-win_amd64.whl", upload-time = 2025-09-29T20:27:46Z, size = 160082, hashes = { sha256 = "5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b" } }, + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", upload-time = 2025-09-25T21:31:46Z, size = 184227, hashes = { sha256 = "214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b" } }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-09-25T21:31:47Z, size = 174019, hashes = { sha256 = "02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956" } }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-25T21:31:49Z, size = 740646, hashes = { sha256 = "b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8" } }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-25T21:31:50Z, size = 840793, hashes = { sha256 = "66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198" } }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-25T21:31:51Z, size = 770293, hashes = { sha256 = "9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b" } }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-09-25T21:31:53Z, size = 732872, hashes = { sha256 = "418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0" } }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-09-25T21:31:54Z, size = 758828, hashes = { sha256 = "5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69" } }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", upload-time = 2025-09-25T21:31:55Z, size = 142415, hashes = { sha256 = "28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e" } }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", upload-time = 2025-09-25T21:31:57Z, size = 158561, hashes = { sha256 = "bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c" } }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", upload-time = 2025-09-25T21:31:58Z, size = 185826, hashes = { sha256 = "44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e" } }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-09-25T21:32:00Z, size = 175577, hashes = { sha256 = "652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824" } }, + { 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", upload-time = 2025-09-25T21:32:01Z, size = 775556, hashes = { sha256 = "10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c" } }, + { 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", upload-time = 2025-09-25T21:32:03Z, size = 882114, hashes = { sha256 = "850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00" } }, + { 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", upload-time = 2025-09-25T21:32:04Z, size = 806638, hashes = { sha256 = "b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d" } }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-09-25T21:32:06Z, size = 767463, hashes = { sha256 = "1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a" } }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-09-25T21:32:07Z, size = 794986, hashes = { sha256 = "37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4" } }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", upload-time = 2025-09-25T21:32:08Z, size = 142543, hashes = { sha256 = "8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b" } }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", upload-time = 2025-09-25T21:32:09Z, size = 158763, hashes = { sha256 = "9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf" } }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2025-09-25T21:32:11Z, size = 182063, hashes = { sha256 = "7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196" } }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-09-25T21:32:12Z, size = 173973, hashes = { sha256 = "fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0" } }, + { 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", upload-time = 2025-09-25T21:32:13Z, size = 775116, hashes = { sha256 = "9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28" } }, + { 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", upload-time = 2025-09-25T21:32:15Z, size = 844011, hashes = { sha256 = "5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c" } }, + { 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", upload-time = 2025-09-25T21:32:16Z, size = 807870, hashes = { sha256 = "ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc" } }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-09-25T21:32:17Z, size = 761089, hashes = { sha256 = "8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e" } }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-09-25T21:32:18Z, size = 790181, hashes = { sha256 = "41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea" } }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", upload-time = 2025-09-25T21:32:20Z, size = 137658, hashes = { sha256 = "96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5" } }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", upload-time = 2025-09-25T21:32:21Z, size = 154003, hashes = { sha256 = "5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b" } }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", upload-time = 2025-09-25T21:32:22Z, size = 140344, hashes = { sha256 = "64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd" } }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-09-25T21:32:23Z, size = 181669, hashes = { sha256 = "8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8" } }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-09-25T21:32:25Z, size = 173252, hashes = { sha256 = "2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1" } }, + { 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", upload-time = 2025-09-25T21:32:26Z, size = 767081, hashes = { sha256 = "ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c" } }, + { 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", upload-time = 2025-09-25T21:32:27Z, size = 841159, hashes = { sha256 = "a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5" } }, + { 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", upload-time = 2025-09-25T21:32:28Z, size = 801626, hashes = { sha256 = "0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6" } }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-09-25T21:32:30Z, size = 753613, hashes = { sha256 = "f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6" } }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-09-25T21:32:31Z, size = 794115, hashes = { sha256 = "eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be" } }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", upload-time = 2025-09-25T21:32:32Z, size = 137427, hashes = { sha256 = "d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26" } }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", upload-time = 2025-09-25T21:32:33Z, size = 154090, hashes = { sha256 = "79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c" } }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", upload-time = 2025-09-25T21:32:34Z, size = 140246, hashes = { sha256 = "5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb" } }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", upload-time = 2025-09-25T21:32:35Z, size = 181814, hashes = { sha256 = "8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac" } }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-09-25T21:32:36Z, size = 173809, hashes = { sha256 = "34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310" } }, + { 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", upload-time = 2025-09-25T21:32:37Z, size = 766454, hashes = { sha256 = "501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7" } }, + { 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", upload-time = 2025-09-25T21:32:39Z, size = 836355, hashes = { sha256 = "b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788" } }, + { 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", upload-time = 2025-09-25T21:32:40Z, size = 794175, hashes = { sha256 = "c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5" } }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-09-25T21:32:42Z, size = 755228, hashes = { sha256 = "7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764" } }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-09-25T21:32:43Z, size = 789194, hashes = { sha256 = "5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35" } }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", upload-time = 2025-09-25T21:32:57Z, size = 156429, hashes = { sha256 = "4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac" } }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", upload-time = 2025-09-25T21:32:59Z, size = 143912, hashes = { sha256 = "93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3" } }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", upload-time = 2025-09-25T21:32:44Z, size = 189108, hashes = { sha256 = "02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3" } }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-09-25T21:32:45Z, size = 183641, hashes = { sha256 = "c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba" } }, + { 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", upload-time = 2025-09-25T21:32:48Z, size = 831901, hashes = { sha256 = "6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c" } }, + { 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", upload-time = 2025-09-25T21:32:50Z, size = 861132, hashes = { sha256 = "a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702" } }, + { 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", upload-time = 2025-09-25T21:32:51Z, size = 839261, hashes = { sha256 = "00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c" } }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", upload-time = 2025-09-25T21:32:52Z, size = 805272, hashes = { sha256 = "66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065" } }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", upload-time = 2025-09-25T21:32:54Z, size = 829923, hashes = { sha256 = "16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65" } }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", upload-time = 2025-09-25T21:32:55Z, size = 174062, hashes = { sha256 = "4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9" } }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", upload-time = 2025-09-25T21:32:56Z, size = 149341, hashes = { sha256 = "ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b" } }, + { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", upload-time = 2025-09-25T21:33:00Z, size = 184450, hashes = { sha256 = "b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da" } }, + { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2025-09-25T21:33:02Z, size = 174319, hashes = { sha256 = "c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917" } }, + { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-25T21:33:03Z, size = 737631, hashes = { sha256 = "3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9" } }, + { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-25T21:33:05Z, size = 836795, hashes = { sha256 = "5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5" } }, + { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-25T21:33:06Z, size = 750767, hashes = { sha256 = "0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a" } }, + { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", upload-time = 2025-09-25T21:33:08Z, size = 727982, hashes = { sha256 = "fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926" } }, + { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", upload-time = 2025-09-25T21:33:09Z, size = 755677, hashes = { sha256 = "27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7" } }, + { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", upload-time = 2025-09-25T21:33:10Z, size = 142592, hashes = { sha256 = "1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0" } }, + { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", upload-time = 2025-09-25T21:33:15Z, size = 158777, hashes = { sha256 = "2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007" } }, +] + +[[packages]] +name = "pyyaml-env-tag" +version = "1.1" +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", upload-time = 2025-05-13T15:24:01Z, size = 5737, hashes = { sha256 = "2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", upload-time = 2025-05-13T15:23:59Z, size = 4722, hashes = { sha256 = "17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04" } }] + +[[packages]] name = "rapidfuzz" -version = "3.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/6895abc3a3d056b9698da3199b04c0e56226d530ae44a470edabf8b664f0/rapidfuzz-3.13.0.tar.gz", hash = "sha256:d2eaf3839e52cbcc0accbe9817a67b4b0fcf70aaeb229cfddc1c28061f9ce5d8", size = 57904226 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/76/606e71e4227790750f1646f3c5c873e18d6cfeb6f9a77b2b8c4dec8f0f66/rapidfuzz-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:09e908064d3684c541d312bd4c7b05acb99a2c764f6231bd507d4b4b65226c23", size = 1982282 }, - { url = "https://files.pythonhosted.org/packages/0a/f5/d0b48c6b902607a59fd5932a54e3518dae8223814db8349b0176e6e9444b/rapidfuzz-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:57c390336cb50d5d3bfb0cfe1467478a15733703af61f6dffb14b1cd312a6fae", size = 1439274 }, - { url = "https://files.pythonhosted.org/packages/59/cf/c3ac8c80d8ced6c1f99b5d9674d397ce5d0e9d0939d788d67c010e19c65f/rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0da54aa8547b3c2c188db3d1c7eb4d1bb6dd80baa8cdaeaec3d1da3346ec9caa", size = 1399854 }, - { url = "https://files.pythonhosted.org/packages/09/5d/ca8698e452b349c8313faf07bfa84e7d1c2d2edf7ccc67bcfc49bee1259a/rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df8e8c21e67afb9d7fbe18f42c6111fe155e801ab103c81109a61312927cc611", size = 5308962 }, - { url = "https://files.pythonhosted.org/packages/66/0a/bebada332854e78e68f3d6c05226b23faca79d71362509dbcf7b002e33b7/rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:461fd13250a2adf8e90ca9a0e1e166515cbcaa5e9c3b1f37545cbbeff9e77f6b", size = 1625016 }, - { url = "https://files.pythonhosted.org/packages/de/0c/9e58d4887b86d7121d1c519f7050d1be5eb189d8a8075f5417df6492b4f5/rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2b3dd5d206a12deca16870acc0d6e5036abeb70e3cad6549c294eff15591527", size = 1600414 }, - { url = "https://files.pythonhosted.org/packages/9b/df/6096bc669c1311568840bdcbb5a893edc972d1c8d2b4b4325c21d54da5b1/rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1343d745fbf4688e412d8f398c6e6d6f269db99a54456873f232ba2e7aeb4939", size = 3053179 }, - { url = "https://files.pythonhosted.org/packages/f9/46/5179c583b75fce3e65a5cd79a3561bd19abd54518cb7c483a89b284bf2b9/rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b1b065f370d54551dcc785c6f9eeb5bd517ae14c983d2784c064b3aa525896df", size = 2456856 }, - { url = "https://files.pythonhosted.org/packages/6b/64/e9804212e3286d027ac35bbb66603c9456c2bce23f823b67d2f5cabc05c1/rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:11b125d8edd67e767b2295eac6eb9afe0b1cdc82ea3d4b9257da4b8e06077798", size = 7567107 }, - { url = "https://files.pythonhosted.org/packages/8a/f2/7d69e7bf4daec62769b11757ffc31f69afb3ce248947aadbb109fefd9f65/rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c33f9c841630b2bb7e69a3fb5c84a854075bb812c47620978bddc591f764da3d", size = 2854192 }, - { url = "https://files.pythonhosted.org/packages/05/21/ab4ad7d7d0f653e6fe2e4ccf11d0245092bef94cdff587a21e534e57bda8/rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae4574cb66cf1e85d32bb7e9ec45af5409c5b3970b7ceb8dea90168024127566", size = 3398876 }, - { url = "https://files.pythonhosted.org/packages/0f/a8/45bba94c2489cb1ee0130dcb46e1df4fa2c2b25269e21ffd15240a80322b/rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e05752418b24bbd411841b256344c26f57da1148c5509e34ea39c7eb5099ab72", size = 4377077 }, - { url = "https://files.pythonhosted.org/packages/0c/f3/5e0c6ae452cbb74e5436d3445467447e8c32f3021f48f93f15934b8cffc2/rapidfuzz-3.13.0-cp313-cp313-win32.whl", hash = "sha256:0e1d08cb884805a543f2de1f6744069495ef527e279e05370dd7c83416af83f8", size = 1822066 }, - { url = "https://files.pythonhosted.org/packages/96/e3/a98c25c4f74051df4dcf2f393176b8663bfd93c7afc6692c84e96de147a2/rapidfuzz-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9a7c6232be5f809cd39da30ee5d24e6cadd919831e6020ec6c2391f4c3bc9264", size = 1615100 }, - { url = "https://files.pythonhosted.org/packages/60/b1/05cd5e697c00cd46d7791915f571b38c8531f714832eff2c5e34537c49ee/rapidfuzz-3.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:3f32f15bacd1838c929b35c84b43618481e1b3d7a61b5ed2db0291b70ae88b53", size = 858976 }, -] - -[[package]] +version = "3.14.1" +sdist = { url = "https://files.pythonhosted.org/packages/ed/fc/a98b616db9a42dcdda7c78c76bdfdf6fe290ac4c5ffbb186f73ec981ad5b/rapidfuzz-3.14.1.tar.gz", upload-time = 2025-09-08T21:08:15Z, size = 57869570, hashes = { sha256 = "b02850e7f7152bd1edff27e9d584505b84968cacedee7a734ec4050c655a803c" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/b9/4e35178f405a1a95abd37cce4dc09d4a5bbc5e098687680b5ba796d3115b/rapidfuzz-3.14.1-cp310-cp310-macosx_10_9_x86_64.whl", upload-time = 2025-09-08T21:05:16Z, size = 1939645, hashes = { sha256 = "489440e4b5eea0d150a31076eb183bed0ec84f934df206c72ae4fc3424501758" } }, + { url = "https://files.pythonhosted.org/packages/51/af/fd7b8662a3b6952559af322dcf1c9d4eb5ec6be2697c30ae8ed3c44876ca/rapidfuzz-3.14.1-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-09-08T21:05:18Z, size = 1393620, hashes = { sha256 = "eff22cc938c3f74d194df03790a6c3325d213b28cf65cdefd6fdeae759b745d5" } }, + { url = "https://files.pythonhosted.org/packages/c5/5b/5715445e29c1c6ba364b3d27278da3fdffb18d9147982e977c6638dcecbf/rapidfuzz-3.14.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-08T21:05:20Z, size = 1387721, hashes = { sha256 = "e0307f018b16feaa36074bcec2496f6f120af151a098910296e72e233232a62f" } }, + { url = "https://files.pythonhosted.org/packages/19/49/83a14a6a90982b090257c4b2e96b9b9c423a89012b8504d5a14d92a4f8c2/rapidfuzz-3.14.1-cp310-cp310-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-08T21:05:22Z, size = 1694545, hashes = { sha256 = "bc133652da143aca1ab72de235446432888b2b7f44ee332d006f8207967ecb8a" } }, + { url = "https://files.pythonhosted.org/packages/99/f7/94618fcaaac8c04abf364f405c6811a02bc9edef209f276dc513a9a50f7c/rapidfuzz-3.14.1-cp310-cp310-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-08T21:05:23Z, size = 2237075, hashes = { sha256 = "e9e71b3fe7e4a1590843389a90fe2a8684649fc74b9b7446e17ee504ddddb7de" } }, + { url = "https://files.pythonhosted.org/packages/58/f6/a5ee2db25f36b0e5e06502fb77449b7718cd9f92ad36d598e669ba91db7b/rapidfuzz-3.14.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-08T21:05:25Z, size = 3168778, hashes = { sha256 = "6c51519eb2f20b52eba6fc7d857ae94acc6c2a1f5d0f2d794b9d4977cdc29dd7" } }, + { url = "https://files.pythonhosted.org/packages/0f/e8/c9620e358805c099e6755b7d2827b1e711b5e61914d6112ce2faa2c2af79/rapidfuzz-3.14.1-cp310-cp310-manylinux_2_31_armv7l.whl", upload-time = 2025-09-08T21:05:27Z, size = 1223827, hashes = { sha256 = "fe87d94602624f8f25fff9a0a7b47f33756c4d9fc32b6d3308bb142aa483b8a4" } }, + { url = "https://files.pythonhosted.org/packages/84/08/24916c3c3d55d6236474c9da0a595641d0013d3604de0625e8a8974371c3/rapidfuzz-3.14.1-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-09-08T21:05:28Z, size = 2408366, hashes = { sha256 = "2d665380503a575dda52eb712ea521f789e8f8fd629c7a8e6c0f8ff480febc78" } }, + { url = "https://files.pythonhosted.org/packages/40/d4/4152e8821b5c548443a6c46568fccef13de5818a5ab370d553ea3d5955b3/rapidfuzz-3.14.1-cp310-cp310-musllinux_1_2_armv7l.whl", upload-time = 2025-09-08T21:05:30Z, size = 2530148, hashes = { sha256 = "c0f0dd022b8a7cbf3c891f6de96a80ab6a426f1069a085327816cea749e096c2" } }, + { url = "https://files.pythonhosted.org/packages/bd/af/6587c6d590abe232c530ad43fbfbcaec899bff7204e237f1fd21e2e44b81/rapidfuzz-3.14.1-cp310-cp310-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-08T21:05:32Z, size = 2810628, hashes = { sha256 = "bf1ba22d36858b265c95cd774ba7fe8991e80a99cd86fe4f388605b01aee81a3" } }, + { url = "https://files.pythonhosted.org/packages/d7/90/a99e6cfd90feb9d770654f1f39321099bbbf7f85d2832f2ef48d3f4ebc5f/rapidfuzz-3.14.1-cp310-cp310-musllinux_1_2_s390x.whl", upload-time = 2025-09-08T21:05:34Z, size = 3314406, hashes = { sha256 = "ca1c1494ac9f9386d37f0e50cbaf4d07d184903aed7691549df1b37e9616edc9" } }, + { url = "https://files.pythonhosted.org/packages/5f/b3/eba5a6c217200fd1d3615997930a9e5db6a74e3002b7867b54545f9b5cbb/rapidfuzz-3.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-09-08T21:05:36Z, size = 4280030, hashes = { sha256 = "9e4b12e921b0fa90d7c2248742a536f21eae5562174090b83edd0b4ab8b557d7" } }, + { url = "https://files.pythonhosted.org/packages/04/6f/d2e060a2094cfb7f3cd487c376e098abb22601e0eea178e51a59ce0a3158/rapidfuzz-3.14.1-cp310-cp310-win32.whl", upload-time = 2025-09-08T21:05:38Z, size = 1727070, hashes = { sha256 = "5e1c1f2292baa4049535b07e9e81feb29e3650d2ba35ee491e64aca7ae4cb15e" } }, + { url = "https://files.pythonhosted.org/packages/73/0a/ca231464ec689f2aabf9547a52cbc76a10affe960bddde8660699ba3de33/rapidfuzz-3.14.1-cp310-cp310-win_amd64.whl", upload-time = 2025-09-08T21:05:40Z, size = 1545335, hashes = { sha256 = "59a8694beb9a13c4090ab3d1712cabbd896c6949706d1364e2a2e1713c413760" } }, + { url = "https://files.pythonhosted.org/packages/59/c5/1e0b17f20fd3d701470548a6db8f36d589fb1a8a65d3828968547d987486/rapidfuzz-3.14.1-cp310-cp310-win_arm64.whl", upload-time = 2025-09-08T21:05:42Z, size = 816960, hashes = { sha256 = "e94cee93faa792572c574a615abe12912124b4ffcf55876b72312914ab663345" } }, + { url = "https://files.pythonhosted.org/packages/5c/c7/c3c860d512606225c11c8ee455b4dc0b0214dbcfac90a2c22dddf55320f3/rapidfuzz-3.14.1-cp311-cp311-macosx_10_9_x86_64.whl", upload-time = 2025-09-08T21:05:44Z, size = 1938398, hashes = { sha256 = "4d976701060886a791c8a9260b1d4139d14c1f1e9a6ab6116b45a1acf3baff67" } }, + { url = "https://files.pythonhosted.org/packages/c0/f3/67f5c5cd4d728993c48c1dcb5da54338d77c03c34b4903cc7839a3b89faf/rapidfuzz-3.14.1-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-09-08T21:05:45Z, size = 1392819, hashes = { sha256 = "5e6ba7e6eb2ab03870dcab441d707513db0b4264c12fba7b703e90e8b4296df2" } }, + { url = "https://files.pythonhosted.org/packages/d5/06/400d44842f4603ce1bebeaeabe776f510e329e7dbf6c71b6f2805e377889/rapidfuzz-3.14.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-08T21:05:47Z, size = 1391798, hashes = { sha256 = "1e532bf46de5fd3a1efde73a16a4d231d011bce401c72abe3c6ecf9de681003f" } }, + { url = "https://files.pythonhosted.org/packages/90/97/a6944955713b47d88e8ca4305ca7484940d808c4e6c4e28b6fa0fcbff97e/rapidfuzz-3.14.1-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-08T21:05:48Z, size = 1699136, hashes = { sha256 = "f9b6a6fb8ed9b951e5f3b82c1ce6b1665308ec1a0da87f799b16e24fc59e4662" } }, + { url = "https://files.pythonhosted.org/packages/a8/1e/f311a5c95ddf922db6dd8666efeceb9ac69e1319ed098ac80068a4041732/rapidfuzz-3.14.1-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-08T21:05:50Z, size = 2236238, hashes = { sha256 = "5b6ac3f9810949caef0e63380b11a3c32a92f26bacb9ced5e32c33560fcdf8d1" } }, + { url = "https://files.pythonhosted.org/packages/85/27/e14e9830255db8a99200f7111b158ddef04372cf6332a415d053fe57cc9c/rapidfuzz-3.14.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-08T21:05:52Z, size = 3183685, hashes = { sha256 = "e52e4c34fd567f77513e886b66029c1ae02f094380d10eba18ba1c68a46d8b90" } }, + { url = "https://files.pythonhosted.org/packages/61/b2/42850c9616ddd2887904e5dd5377912cbabe2776fdc9fd4b25e6e12fba32/rapidfuzz-3.14.1-cp311-cp311-manylinux_2_31_armv7l.whl", upload-time = 2025-09-08T21:05:53Z, size = 1231523, hashes = { sha256 = "2ef72e41b1a110149f25b14637f1cedea6df192462120bea3433980fe9d8ac05" } }, + { url = "https://files.pythonhosted.org/packages/de/b5/6b90ed7127a1732efef39db46dd0afc911f979f215b371c325a2eca9cb15/rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-09-08T21:05:55Z, size = 2415209, hashes = { sha256 = "fb654a35b373d712a6b0aa2a496b2b5cdd9d32410cfbaecc402d7424a90ba72a" } }, + { url = "https://files.pythonhosted.org/packages/70/60/af51c50d238c82f2179edc4b9f799cc5a50c2c0ebebdcfaa97ded7d02978/rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_armv7l.whl", upload-time = 2025-09-08T21:05:57Z, size = 2532957, hashes = { sha256 = "2b2c12e5b9eb8fe9a51b92fe69e9ca362c0970e960268188a6d295e1dec91e6d" } }, + { url = "https://files.pythonhosted.org/packages/50/92/29811d2ba7c984251a342c4f9ccc7cc4aa09d43d800af71510cd51c36453/rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-08T21:05:58Z, size = 2815720, hashes = { sha256 = "4f069dec5c450bd987481e752f0a9979e8fdf8e21e5307f5058f5c4bb162fa56" } }, + { url = "https://files.pythonhosted.org/packages/78/69/cedcdee16a49e49d4985eab73b59447f211736c5953a58f1b91b6c53a73f/rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_s390x.whl", upload-time = 2025-09-08T21:06:00Z, size = 3323704, hashes = { sha256 = "4d0d9163725b7ad37a8c46988cae9ebab255984db95ad01bf1987ceb9e3058dd" } }, + { url = "https://files.pythonhosted.org/packages/76/3e/5a3f9a5540f18e0126e36f86ecf600145344acb202d94b63ee45211a18b8/rapidfuzz-3.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-09-08T21:06:02Z, size = 4287341, hashes = { sha256 = "db656884b20b213d846f6bc990c053d1f4a60e6d4357f7211775b02092784ca1" } }, + { url = "https://files.pythonhosted.org/packages/46/26/45db59195929dde5832852c9de8533b2ac97dcc0d852d1f18aca33828122/rapidfuzz-3.14.1-cp311-cp311-win32.whl", upload-time = 2025-09-08T21:06:04Z, size = 1726574, hashes = { sha256 = "4b42f7b9c58cbcfbfaddc5a6278b4ca3b6cd8983e7fd6af70ca791dff7105fb9" } }, + { url = "https://files.pythonhosted.org/packages/01/5c/a4caf76535f35fceab25b2aaaed0baecf15b3d1fd40746f71985d20f8c4b/rapidfuzz-3.14.1-cp311-cp311-win_amd64.whl", upload-time = 2025-09-08T21:06:06Z, size = 1547124, hashes = { sha256 = "e5847f30d7d4edefe0cb37294d956d3495dd127c1c56e9128af3c2258a520bb4" } }, + { url = "https://files.pythonhosted.org/packages/c6/66/aa93b52f95a314584d71fa0b76df00bdd4158aafffa76a350f1ae416396c/rapidfuzz-3.14.1-cp311-cp311-win_arm64.whl", upload-time = 2025-09-08T21:06:07Z, size = 816958, hashes = { sha256 = "5087d8ad453092d80c042a08919b1cb20c8ad6047d772dc9312acd834da00f75" } }, + { url = "https://files.pythonhosted.org/packages/df/77/2f4887c9b786f203e50b816c1cde71f96642f194e6fa752acfa042cf53fd/rapidfuzz-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2025-09-08T21:06:09Z, size = 1932216, hashes = { sha256 = "809515194f628004aac1b1b280c3734c5ea0ccbd45938c9c9656a23ae8b8f553" } }, + { url = "https://files.pythonhosted.org/packages/de/bd/b5e445d156cb1c2a87d36d8da53daf4d2a1d1729b4851660017898b49aa0/rapidfuzz-3.14.1-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-09-08T21:06:10Z, size = 1393414, hashes = { sha256 = "0afcf2d6cb633d0d4260d8df6a40de2d9c93e9546e2c6b317ab03f89aa120ad7" } }, + { url = "https://files.pythonhosted.org/packages/de/bd/98d065dd0a4479a635df855616980eaae1a1a07a876db9400d421b5b6371/rapidfuzz-3.14.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-08T21:06:12Z, size = 1377194, hashes = { sha256 = "5c1c3d07d53dcafee10599da8988d2b1f39df236aee501ecbd617bd883454fcd" } }, + { url = "https://files.pythonhosted.org/packages/d3/8a/1265547b771128b686f3c431377ff1db2fa073397ed082a25998a7b06d4e/rapidfuzz-3.14.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-08T21:06:14Z, size = 1669573, hashes = { sha256 = "6e9ee3e1eb0a027717ee72fe34dc9ac5b3e58119f1bd8dd15bc19ed54ae3e62b" } }, + { url = "https://files.pythonhosted.org/packages/a8/57/e73755c52fb451f2054196404ccc468577f8da023b3a48c80bce29ee5d4a/rapidfuzz-3.14.1-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-08T21:06:15Z, size = 2217833, hashes = { sha256 = "70c845b64a033a20c44ed26bc890eeb851215148cc3e696499f5f65529afb6cb" } }, + { url = "https://files.pythonhosted.org/packages/20/14/7399c18c460e72d1b754e80dafc9f65cb42a46cc8f29cd57d11c0c4acc94/rapidfuzz-3.14.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-08T21:06:17Z, size = 3159012, hashes = { sha256 = "26db0e815213d04234298dea0d884d92b9cb8d4ba954cab7cf67a35853128a33" } }, + { url = "https://files.pythonhosted.org/packages/f8/5e/24f0226ddb5440cabd88605d2491f99ae3748a6b27b0bc9703772892ced7/rapidfuzz-3.14.1-cp312-cp312-manylinux_2_31_armv7l.whl", upload-time = 2025-09-08T21:06:21Z, size = 1227032, hashes = { sha256 = "6ad3395a416f8b126ff11c788531f157c7debeb626f9d897c153ff8980da10fb" } }, + { url = "https://files.pythonhosted.org/packages/40/43/1d54a4ad1a5fac2394d5f28a3108e2bf73c26f4f23663535e3139cfede9b/rapidfuzz-3.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-09-08T21:06:23Z, size = 2395054, hashes = { sha256 = "61c5b9ab6f730e6478aa2def566223712d121c6f69a94c7cc002044799442afd" } }, + { url = "https://files.pythonhosted.org/packages/0c/71/e9864cd5b0f086c4a03791f5dfe0155a1b132f789fe19b0c76fbabd20513/rapidfuzz-3.14.1-cp312-cp312-musllinux_1_2_armv7l.whl", upload-time = 2025-09-08T21:06:26Z, size = 2524741, hashes = { sha256 = "13e0ea3d0c533969158727d1bb7a08c2cc9a816ab83f8f0dcfde7e38938ce3e6" } }, + { url = "https://files.pythonhosted.org/packages/b2/0c/53f88286b912faf4a3b2619a60df4f4a67bd0edcf5970d7b0c1143501f0c/rapidfuzz-3.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-08T21:06:29Z, size = 2785311, hashes = { sha256 = "6325ca435b99f4001aac919ab8922ac464999b100173317defb83eae34e82139" } }, + { url = "https://files.pythonhosted.org/packages/53/9a/229c26dc4f91bad323f07304ee5ccbc28f0d21c76047a1e4f813187d0bad/rapidfuzz-3.14.1-cp312-cp312-musllinux_1_2_s390x.whl", upload-time = 2025-09-08T21:06:31Z, size = 3303630, hashes = { sha256 = "07a9fad3247e68798424bdc116c1094e88ecfabc17b29edf42a777520347648e" } }, + { url = "https://files.pythonhosted.org/packages/05/de/20e330d6d58cbf83da914accd9e303048b7abae2f198886f65a344b69695/rapidfuzz-3.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-09-08T21:06:32Z, size = 4262364, hashes = { sha256 = "f8ff5dbe78db0a10c1f916368e21d328935896240f71f721e073cf6c4c8cdedd" } }, + { url = "https://files.pythonhosted.org/packages/1f/10/2327f83fad3534a8d69fe9cd718f645ec1fe828b60c0e0e97efc03bf12f8/rapidfuzz-3.14.1-cp312-cp312-win32.whl", upload-time = 2025-09-08T21:06:34Z, size = 1711927, hashes = { sha256 = "9c83270e44a6ae7a39fc1d7e72a27486bccc1fa5f34e01572b1b90b019e6b566" } }, + { url = "https://files.pythonhosted.org/packages/78/8d/199df0370133fe9f35bc72f3c037b53c93c5c1fc1e8d915cf7c1f6bb8557/rapidfuzz-3.14.1-cp312-cp312-win_amd64.whl", upload-time = 2025-09-08T21:06:36Z, size = 1542045, hashes = { sha256 = "e06664c7fdb51c708e082df08a6888fce4c5c416d7e3cc2fa66dd80eb76a149d" } }, + { url = "https://files.pythonhosted.org/packages/b3/c6/cc5d4bd1b16ea2657c80b745d8b1c788041a31fad52e7681496197b41562/rapidfuzz-3.14.1-cp312-cp312-win_arm64.whl", upload-time = 2025-09-08T21:06:38Z, size = 813170, hashes = { sha256 = "6c7c26025f7934a169a23dafea6807cfc3fb556f1dd49229faf2171e5d8101cc" } }, + { url = "https://files.pythonhosted.org/packages/0d/f2/0024cc8eead108c4c29337abe133d72ddf3406ce9bbfbcfc110414a7ea07/rapidfuzz-3.14.1-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-09-08T21:06:39Z, size = 1926515, hashes = { sha256 = "8d69f470d63ee824132ecd80b1974e1d15dd9df5193916901d7860cef081a260" } }, + { url = "https://files.pythonhosted.org/packages/12/ae/6cb211f8930bea20fa989b23f31ee7f92940caaf24e3e510d242a1b28de4/rapidfuzz-3.14.1-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-09-08T21:06:41Z, size = 1388431, hashes = { sha256 = "6f571d20152fc4833b7b5e781b36d5e4f31f3b5a596a3d53cf66a1bd4436b4f4" } }, + { url = "https://files.pythonhosted.org/packages/39/88/bfec24da0607c39e5841ced5594ea1b907d20f83adf0e3ee87fa454a425b/rapidfuzz-3.14.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-08T21:06:43Z, size = 1375664, hashes = { sha256 = "61d77e09b2b6bc38228f53b9ea7972a00722a14a6048be9a3672fb5cb08bad3a" } }, + { url = "https://files.pythonhosted.org/packages/f4/43/9f282ba539e404bdd7052c7371d3aaaa1a9417979d2a1d8332670c7f385a/rapidfuzz-3.14.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-08T21:06:45Z, size = 1668113, hashes = { sha256 = "8b41d95ef86a6295d353dc3bb6c80550665ba2c3bef3a9feab46074d12a9af8f" } }, + { url = "https://files.pythonhosted.org/packages/7f/2f/0b3153053b1acca90969eb0867922ac8515b1a8a48706a3215c2db60e87c/rapidfuzz-3.14.1-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-08T21:06:47Z, size = 2212875, hashes = { sha256 = "0591df2e856ad583644b40a2b99fb522f93543c65e64b771241dda6d1cfdc96b" } }, + { url = "https://files.pythonhosted.org/packages/f8/9b/623001dddc518afaa08ed1fbbfc4005c8692b7a32b0f08b20c506f17a770/rapidfuzz-3.14.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-08T21:06:49Z, size = 3161181, hashes = { sha256 = "f277801f55b2f3923ef2de51ab94689a0671a4524bf7b611de979f308a54cd6f" } }, + { url = "https://files.pythonhosted.org/packages/ce/b7/d8404ed5ad56eb74463e5ebf0a14f0019d7eb0e65e0323f709fe72e0884c/rapidfuzz-3.14.1-cp313-cp313-manylinux_2_31_armv7l.whl", upload-time = 2025-09-08T21:06:51Z, size = 1225495, hashes = { sha256 = "893fdfd4f66ebb67f33da89eb1bd1674b7b30442fdee84db87f6cb9074bf0ce9" } }, + { url = "https://files.pythonhosted.org/packages/2c/6c/b96af62bc7615d821e3f6b47563c265fd7379d7236dfbc1cbbcce8beb1d2/rapidfuzz-3.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-09-08T21:06:53Z, size = 2396294, hashes = { sha256 = "fe2651258c1f1afa9b66f44bf82f639d5f83034f9804877a1bbbae2120539ad1" } }, + { url = "https://files.pythonhosted.org/packages/7f/b7/c60c9d22a7debed8b8b751f506a4cece5c22c0b05e47a819d6b47bc8c14e/rapidfuzz-3.14.1-cp313-cp313-musllinux_1_2_armv7l.whl", upload-time = 2025-09-08T21:06:55Z, size = 2529629, hashes = { sha256 = "ace21f7a78519d8e889b1240489cd021c5355c496cb151b479b741a4c27f0a25" } }, + { url = "https://files.pythonhosted.org/packages/25/94/a9ec7ccb28381f14de696ffd51c321974762f137679df986f5375d35264f/rapidfuzz-3.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-08T21:06:57Z, size = 2782960, hashes = { sha256 = "cb5acf24590bc5e57027283b015950d713f9e4d155fda5cfa71adef3b3a84502" } }, + { url = "https://files.pythonhosted.org/packages/68/80/04e5276d223060eca45250dbf79ea39940c0be8b3083661d58d57572c2c5/rapidfuzz-3.14.1-cp313-cp313-musllinux_1_2_s390x.whl", upload-time = 2025-09-08T21:06:59Z, size = 3298427, hashes = { sha256 = "67ea46fa8cc78174bad09d66b9a4b98d3068e85de677e3c71ed931a1de28171f" } }, + { url = "https://files.pythonhosted.org/packages/4a/63/24759b2a751562630b244e68ccaaf7a7525c720588fcc77c964146355aee/rapidfuzz-3.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-09-08T21:07:01Z, size = 4267736, hashes = { sha256 = "44e741d785de57d1a7bae03599c1cbc7335d0b060a35e60c44c382566e22782e" } }, + { url = "https://files.pythonhosted.org/packages/18/a4/73f1b1f7f44d55f40ffbffe85e529eb9d7e7f7b2ffc0931760eadd163995/rapidfuzz-3.14.1-cp313-cp313-win32.whl", upload-time = 2025-09-08T21:07:03Z, size = 1710515, hashes = { sha256 = "b1fe6001baa9fa36bcb565e24e88830718f6c90896b91ceffcb48881e3adddbc" } }, + { url = "https://files.pythonhosted.org/packages/6a/8b/a8fe5a6ee4d06fd413aaa9a7e0a23a8630c4b18501509d053646d18c2aa7/rapidfuzz-3.14.1-cp313-cp313-win_amd64.whl", upload-time = 2025-09-08T21:07:05Z, size = 1540081, hashes = { sha256 = "83b8cc6336709fa5db0579189bfd125df280a554af544b2dc1c7da9cdad7e44d" } }, + { url = "https://files.pythonhosted.org/packages/ac/fe/4b0ac16c118a2367d85450b45251ee5362661e9118a1cef88aae1765ffff/rapidfuzz-3.14.1-cp313-cp313-win_arm64.whl", upload-time = 2025-09-08T21:07:07Z, size = 812725, hashes = { sha256 = "cf75769662eadf5f9bd24e865c19e5ca7718e879273dce4e7b3b5824c4da0eb4" } }, + { url = "https://files.pythonhosted.org/packages/e2/cb/1ad9a76d974d153783f8e0be8dbe60ec46488fac6e519db804e299e0da06/rapidfuzz-3.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", upload-time = 2025-09-08T21:07:08Z, size = 1945173, hashes = { sha256 = "d937dbeda71c921ef6537c6d41a84f1b8112f107589c9977059de57a1d726dd6" } }, + { url = "https://files.pythonhosted.org/packages/d9/61/959ed7460941d8a81cbf6552b9c45564778a36cf5e5aa872558b30fc02b2/rapidfuzz-3.14.1-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-09-08T21:07:11Z, size = 1413949, hashes = { sha256 = "7a2d80cc1a4fcc7e259ed4f505e70b36433a63fa251f1bb69ff279fe376c5efd" } }, + { url = "https://files.pythonhosted.org/packages/7b/a0/f46fca44457ca1f25f23cc1f06867454fc3c3be118cd10b552b0ab3e58a2/rapidfuzz-3.14.1-cp313-cp313t-win32.whl", upload-time = 2025-09-08T21:07:12Z, size = 1760666, hashes = { sha256 = "40875e0c06f1a388f1cab3885744f847b557e0b1642dfc31ff02039f9f0823ef" } }, + { url = "https://files.pythonhosted.org/packages/9b/d0/7a5d9c04446f8b66882b0fae45b36a838cf4d31439b5d1ab48a9d17c8e57/rapidfuzz-3.14.1-cp313-cp313t-win_amd64.whl", upload-time = 2025-09-08T21:07:14Z, size = 1579760, hashes = { sha256 = "876dc0c15552f3d704d7fb8d61bdffc872ff63bedf683568d6faad32e51bbce8" } }, + { url = "https://files.pythonhosted.org/packages/4e/aa/2c03ae112320d0746f2c869cae68c413f3fe3b6403358556f2b747559723/rapidfuzz-3.14.1-cp313-cp313t-win_arm64.whl", upload-time = 2025-09-08T21:07:17Z, size = 832088, hashes = { sha256 = "61458e83b0b3e2abc3391d0953c47d6325e506ba44d6a25c869c4401b3bc222c" } }, + { url = "https://files.pythonhosted.org/packages/d6/36/53debca45fbe693bd6181fb05b6a2fd561c87669edb82ec0d7c1961a43f0/rapidfuzz-3.14.1-cp314-cp314-macosx_10_13_x86_64.whl", upload-time = 2025-09-08T21:07:18Z, size = 1926336, hashes = { sha256 = "e84d9a844dc2e4d5c4cabd14c096374ead006583304333c14a6fbde51f612a44" } }, + { url = "https://files.pythonhosted.org/packages/ae/32/b874f48609665fcfeaf16cbaeb2bbc210deef2b88e996c51cfc36c3eb7c3/rapidfuzz-3.14.1-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-09-08T21:07:20Z, size = 1389653, hashes = { sha256 = "40301b93b99350edcd02dbb22e37ca5f2a75d0db822e9b3c522da451a93d6f27" } }, + { url = "https://files.pythonhosted.org/packages/97/25/f6c5a1ff4ec11edadacb270e70b8415f51fa2f0d5730c2c552b81651fbe3/rapidfuzz-3.14.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-08T21:07:22Z, size = 1380911, hashes = { sha256 = "fedd5097a44808dddf341466866e5c57a18a19a336565b4ff50aa8f09eb528f6" } }, + { url = "https://files.pythonhosted.org/packages/d8/f3/d322202ef8fab463759b51ebfaa33228100510c82e6153bd7a922e150270/rapidfuzz-3.14.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-08T21:07:24Z, size = 1673515, hashes = { sha256 = "2e3e61c9e80d8c26709d8aa5c51fdd25139c81a4ab463895f8a567f8347b0548" } }, + { url = "https://files.pythonhosted.org/packages/8d/b9/6b2a97f4c6be96cac3749f32301b8cdf751ce5617b1c8934c96586a0662b/rapidfuzz-3.14.1-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-08T21:07:26Z, size = 2219394, hashes = { sha256 = "da011a373722fac6e64687297a1d17dc8461b82cb12c437845d5a5b161bc24b9" } }, + { url = "https://files.pythonhosted.org/packages/11/bf/afb76adffe4406e6250f14ce48e60a7eb05d4624945bd3c044cfda575fbc/rapidfuzz-3.14.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-08T21:07:28Z, size = 3163582, hashes = { sha256 = "5967d571243cfb9ad3710e6e628ab68c421a237b76e24a67ac22ee0ff12784d6" } }, + { url = "https://files.pythonhosted.org/packages/42/34/e6405227560f61e956cb4c5de653b0f874751c5ada658d3532d6c1df328e/rapidfuzz-3.14.1-cp314-cp314-manylinux_2_31_armv7l.whl", upload-time = 2025-09-08T21:07:30Z, size = 1221116, hashes = { sha256 = "474f416cbb9099676de54aa41944c154ba8d25033ee460f87bb23e54af6d01c9" } }, + { url = "https://files.pythonhosted.org/packages/55/e6/5b757e2e18de384b11d1daf59608453f0baf5d5d8d1c43e1a964af4dc19a/rapidfuzz-3.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-09-08T21:07:32Z, size = 2402670, hashes = { sha256 = "ae2d57464b59297f727c4e201ea99ec7b13935f1f056c753e8103da3f2fc2404" } }, + { url = "https://files.pythonhosted.org/packages/43/c4/d753a415fe54531aa882e288db5ed77daaa72e05c1a39e1cbac00d23024f/rapidfuzz-3.14.1-cp314-cp314-musllinux_1_2_armv7l.whl", upload-time = 2025-09-08T21:07:35Z, size = 2521659, hashes = { sha256 = "57047493a1f62f11354c7143c380b02f1b355c52733e6b03adb1cb0fe8fb8816" } }, + { url = "https://files.pythonhosted.org/packages/cd/28/d4e7fe1515430db98f42deb794c7586a026d302fe70f0216b638d89cf10f/rapidfuzz-3.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-08T21:07:37Z, size = 2788552, hashes = { sha256 = "4acc20776f225ee37d69517a237c090b9fa7e0836a0b8bc58868e9168ba6ef6f" } }, + { url = "https://files.pythonhosted.org/packages/4f/00/eab05473af7a2cafb4f3994bc6bf408126b8eec99a569aac6254ac757db4/rapidfuzz-3.14.1-cp314-cp314-musllinux_1_2_s390x.whl", upload-time = 2025-09-08T21:07:39Z, size = 3306261, hashes = { sha256 = "4373f914ff524ee0146919dea96a40a8200ab157e5a15e777a74a769f73d8a4a" } }, + { url = "https://files.pythonhosted.org/packages/d1/31/2feb8dfcfcff6508230cd2ccfdde7a8bf988c6fda142fe9ce5d3eb15704d/rapidfuzz-3.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-09-08T21:07:41Z, size = 4269522, hashes = { sha256 = "37017b84953927807847016620d61251fe236bd4bcb25e27b6133d955bb9cafb" } }, + { url = "https://files.pythonhosted.org/packages/a3/99/250538d73c8fbab60597c3d131a11ef2a634d38b44296ca11922794491ac/rapidfuzz-3.14.1-cp314-cp314-win32.whl", upload-time = 2025-09-08T21:07:44Z, size = 1745018, hashes = { sha256 = "c8d1dd1146539e093b84d0805e8951475644af794ace81d957ca612e3eb31598" } }, + { url = "https://files.pythonhosted.org/packages/c5/15/d50839d20ad0743aded25b08a98ffb872f4bfda4e310bac6c111fcf6ea1f/rapidfuzz-3.14.1-cp314-cp314-win_amd64.whl", upload-time = 2025-09-08T21:07:46Z, size = 1587666, hashes = { sha256 = "f51c7571295ea97387bac4f048d73cecce51222be78ed808263b45c79c40a440" } }, + { url = "https://files.pythonhosted.org/packages/a3/ff/d73fec989213fb6f0b6f15ee4bbdf2d88b0686197951a06b036111cd1c7d/rapidfuzz-3.14.1-cp314-cp314-win_arm64.whl", upload-time = 2025-09-08T21:07:49Z, size = 835780, hashes = { sha256 = "01eab10ec90912d7d28b3f08f6c91adbaf93458a53f849ff70776ecd70dd7a7a" } }, + { url = "https://files.pythonhosted.org/packages/b7/e7/f0a242687143cebd33a1fb165226b73bd9496d47c5acfad93de820a18fa8/rapidfuzz-3.14.1-cp314-cp314t-macosx_10_13_x86_64.whl", upload-time = 2025-09-08T21:07:51Z, size = 1945182, hashes = { sha256 = "60879fcae2f7618403c4c746a9a3eec89327d73148fb6e89a933b78442ff0669" } }, + { url = "https://files.pythonhosted.org/packages/96/29/ca8a3f8525e3d0e7ab49cb927b5fb4a54855f794c9ecd0a0b60a6c96a05f/rapidfuzz-3.14.1-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-09-08T21:07:53Z, size = 1413946, hashes = { sha256 = "f94d61e44db3fc95a74006a394257af90fa6e826c900a501d749979ff495d702" } }, + { url = "https://files.pythonhosted.org/packages/b5/ef/6fd10aa028db19c05b4ac7fe77f5613e4719377f630c709d89d7a538eea2/rapidfuzz-3.14.1-cp314-cp314t-win32.whl", upload-time = 2025-09-08T21:07:55Z, size = 1795851, hashes = { sha256 = "93b6294a3ffab32a9b5f9b5ca048fa0474998e7e8bb0f2d2b5e819c64cb71ec7" } }, + { url = "https://files.pythonhosted.org/packages/e4/30/acd29ebd906a50f9e0f27d5f82a48cf5e8854637b21489bd81a2459985cf/rapidfuzz-3.14.1-cp314-cp314t-win_amd64.whl", upload-time = 2025-09-08T21:07:58Z, size = 1626748, hashes = { sha256 = "6cb56b695421538fdbe2c0c85888b991d833b8637d2f2b41faa79cea7234c000" } }, + { url = "https://files.pythonhosted.org/packages/c1/f4/dfc7b8c46b1044a47f7ca55deceb5965985cff3193906cb32913121e6652/rapidfuzz-3.14.1-cp314-cp314t-win_arm64.whl", upload-time = 2025-09-08T21:08:00Z, size = 853771, hashes = { sha256 = "7cd312c380d3ce9d35c3ec9726b75eee9da50e8a38e89e229a03db2262d3d96b" } }, + { url = "https://files.pythonhosted.org/packages/6d/10/0ed838b296fdac08ecbaa3a220fb4f1d887ff41b0be44fe8eade45bb650e/rapidfuzz-3.14.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", upload-time = 2025-09-08T21:08:02Z, size = 1860246, hashes = { sha256 = "673ce55a9be5b772dade911909e42382c0828b8a50ed7f9168763fa6b9f7054d" } }, + { url = "https://files.pythonhosted.org/packages/a4/70/a08f4a86387dec97508ead51cc7a4b3130d4e62ac0eae938a6d8e1feff14/rapidfuzz-3.14.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", upload-time = 2025-09-08T21:08:04Z, size = 1336749, hashes = { sha256 = "45c62ada1980ebf4c64c4253993cc8daa018c63163f91db63bb3af69cb74c2e3" } }, + { url = "https://files.pythonhosted.org/packages/d4/39/c12f76f69184bcfb9977d6404b2c5dac7dd4d70ee6803e61556e539d0097/rapidfuzz-3.14.1-pp310-pypy310_pp73-win_amd64.whl", upload-time = 2025-09-08T21:08:06Z, size = 1512629, hashes = { sha256 = "4d51efb29c0df0d4f7f64f672a7624c2146527f0745e3572098d753676538800" } }, + { url = "https://files.pythonhosted.org/packages/05/c7/1b17347e30f2b50dd976c54641aa12003569acb1bdaabf45a5cc6f471c58/rapidfuzz-3.14.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", upload-time = 2025-09-08T21:08:09Z, size = 1862602, hashes = { sha256 = "4a21ccdf1bd7d57a1009030527ba8fae1c74bf832d0a08f6b67de8f5c506c96f" } }, + { url = "https://files.pythonhosted.org/packages/09/cf/95d0dacac77eda22499991bd5f304c77c5965fb27348019a48ec3fe4a3f6/rapidfuzz-3.14.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", upload-time = 2025-09-08T21:08:11Z, size = 1339548, hashes = { sha256 = "589fb0af91d3aff318750539c832ea1100dbac2c842fde24e42261df443845f6" } }, + { url = "https://files.pythonhosted.org/packages/b6/58/f515c44ba8c6fa5daa35134b94b99661ced852628c5505ead07b905c3fc7/rapidfuzz-3.14.1-pp311-pypy311_pp73-win_amd64.whl", upload-time = 2025-09-08T21:08:13Z, size = 1513859, hashes = { sha256 = "a4f18092db4825f2517d135445015b40033ed809a41754918a03ef062abe88a0" } }, +] + +[[packages]] name = "redis" -version = "5.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyjwt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/dd/2b37032f4119dff2a2f9bbcaade03221b100ba26051bb96e275de3e5db7a/redis-5.3.0.tar.gz", hash = "sha256:8d69d2dde11a12dc85d0dbf5c45577a5af048e2456f7077d87ad35c1c81c310e", size = 4626288 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/b0/aa601efe12180ba492b02e270554877e68467e66bda5d73e51eaa8ecc78a/redis-5.3.0-py3-none-any.whl", hash = "sha256:f1deeca1ea2ef25c1e4e46b07f4ea1275140526b1feea4c6459c0ec27a10ef83", size = 272836 }, -] +version = "5.2.1" +sdist = { url = "https://files.pythonhosted.org/packages/47/da/d283a37303a995cd36f8b92db85135153dc4f7a8e4441aa827721b442cfb/redis-5.2.1.tar.gz", upload-time = 2024-12-06T09:50:41Z, size = 4608355, hashes = { sha256 = "16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/3c/5f/fa26b9b2672cbe30e07d9a5bdf39cf16e3b80b42916757c5f92bca88e4ba/redis-5.2.1-py3-none-any.whl", upload-time = 2024-12-06T09:50:39Z, size = 261502, hashes = { sha256 = "ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4" } }] -[[package]] +[[packages]] name = "regex" -version = "2024.11.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, - { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, - { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, - { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, - { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, - { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, - { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, - { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, - { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, - { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, - { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, - { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, - { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, - { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, - { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, -] - -[[package]] +version = "2025.9.18" +sdist = { url = "https://files.pythonhosted.org/packages/49/d3/eaa0d28aba6ad1827ad1e716d9a93e1ba963ada61887498297d3da715133/regex-2025.9.18.tar.gz", upload-time = 2025-09-19T00:38:35Z, size = 400917, hashes = { sha256 = "c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d8/7e06171db8e55f917c5b8e89319cea2d86982e3fc46b677f40358223dece/regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", upload-time = 2025-09-19T00:35:05Z, size = 484829, hashes = { sha256 = "12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788" } }, + { url = "https://files.pythonhosted.org/packages/8d/70/bf91bb39e5bedf75ce730ffbaa82ca585584d13335306d637458946b8b9f/regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", upload-time = 2025-09-19T00:35:08Z, size = 288993, hashes = { sha256 = "220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4" } }, + { url = "https://files.pythonhosted.org/packages/fe/89/69f79b28365eda2c46e64c39d617d5f65a2aa451a4c94de7d9b34c2dc80f/regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-09-19T00:35:09Z, size = 286624, hashes = { sha256 = "87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61" } }, + { url = "https://files.pythonhosted.org/packages/44/31/81e62955726c3a14fcc1049a80bc716765af6c055706869de5e880ddc783/regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-19T00:35:11Z, size = 780473, hashes = { sha256 = "34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251" } }, + { url = "https://files.pythonhosted.org/packages/fb/23/07072b7e191fbb6e213dc03b2f5b96f06d3c12d7deaded84679482926fc7/regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-19T00:35:12Z, size = 849290, hashes = { sha256 = "385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746" } }, + { url = "https://files.pythonhosted.org/packages/b3/f0/aec7f6a01f2a112210424d77c6401b9015675fb887ced7e18926df4ae51e/regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-19T00:35:14Z, size = 897335, hashes = { sha256 = "8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2" } }, + { url = "https://files.pythonhosted.org/packages/cc/90/2e5f9da89d260de7d0417ead91a1bc897f19f0af05f4f9323313b76c47f2/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-19T00:35:15Z, size = 789946, hashes = { sha256 = "c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0" } }, + { url = "https://files.pythonhosted.org/packages/2b/d5/1c712c7362f2563d389be66bae131c8bab121a3fabfa04b0b5bfc9e73c51/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-19T00:35:17Z, size = 780787, hashes = { sha256 = "3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8" } }, + { url = "https://files.pythonhosted.org/packages/4f/92/c54cdb4aa41009632e69817a5aa452673507f07e341076735a2f6c46a37c/regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-09-19T00:35:18Z, size = 773632, hashes = { sha256 = "6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea" } }, + { url = "https://files.pythonhosted.org/packages/db/99/75c996dc6a2231a8652d7ad0bfbeaf8a8c77612d335580f520f3ec40e30b/regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-19T00:35:20Z, size = 844104, hashes = { sha256 = "431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8" } }, + { url = "https://files.pythonhosted.org/packages/1c/f7/25aba34cc130cb6844047dbfe9716c9b8f9629fee8b8bec331aa9241b97b/regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", upload-time = 2025-09-19T00:35:22Z, size = 834794, hashes = { sha256 = "0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25" } }, + { url = "https://files.pythonhosted.org/packages/51/eb/64e671beafa0ae29712268421597596d781704973551312b2425831d4037/regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-09-19T00:35:23Z, size = 778535, hashes = { sha256 = "a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29" } }, + { url = "https://files.pythonhosted.org/packages/26/33/c0ebc0b07bd0bf88f716cca240546b26235a07710ea58e271cfe390ae273/regex-2025.9.18-cp310-cp310-win32.whl", upload-time = 2025-09-19T00:35:25Z, size = 264115, hashes = { sha256 = "4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444" } }, + { url = "https://files.pythonhosted.org/packages/59/39/aeb11a4ae68faaec2498512cadae09f2d8a91f1f65730fe62b9bffeea150/regex-2025.9.18-cp310-cp310-win_amd64.whl", upload-time = 2025-09-19T00:35:26Z, size = 276143, hashes = { sha256 = "47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450" } }, + { url = "https://files.pythonhosted.org/packages/29/04/37f2d3fc334a1031fc2767c9d89cec13c2e72207c7e7f6feae8a47f4e149/regex-2025.9.18-cp310-cp310-win_arm64.whl", upload-time = 2025-09-19T00:35:28Z, size = 268473, hashes = { sha256 = "16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442" } }, + { url = "https://files.pythonhosted.org/packages/58/61/80eda662fc4eb32bfedc331f42390974c9e89c7eac1b79cd9eea4d7c458c/regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", upload-time = 2025-09-19T00:35:30Z, size = 484832, hashes = { sha256 = "51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a" } }, + { url = "https://files.pythonhosted.org/packages/a6/d9/33833d9abddf3f07ad48504ddb53fe3b22f353214bbb878a72eee1e3ddbf/regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", upload-time = 2025-09-19T00:35:31Z, size = 288994, hashes = { sha256 = "828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8" } }, + { url = "https://files.pythonhosted.org/packages/2a/b3/526ee96b0d70ea81980cbc20c3496fa582f775a52e001e2743cc33b2fa75/regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-09-19T00:35:33Z, size = 286619, hashes = { sha256 = "c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414" } }, + { url = "https://files.pythonhosted.org/packages/65/4f/c2c096b02a351b33442aed5895cdd8bf87d372498d2100927c5a053d7ba3/regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-19T00:35:35Z, size = 792454, hashes = { sha256 = "726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a" } }, + { url = "https://files.pythonhosted.org/packages/24/15/b562c9d6e47c403c4b5deb744f8b4bf6e40684cf866c7b077960a925bdff/regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-19T00:35:36Z, size = 858723, hashes = { sha256 = "f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4" } }, + { url = "https://files.pythonhosted.org/packages/f2/01/dba305409849e85b8a1a681eac4c03ed327d8de37895ddf9dc137f59c140/regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-19T00:35:38Z, size = 905899, hashes = { sha256 = "dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a" } }, + { url = "https://files.pythonhosted.org/packages/fe/d0/c51d1e6a80eab11ef96a4cbad17fc0310cf68994fb01a7283276b7e5bbd6/regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-19T00:35:40Z, size = 798981, hashes = { sha256 = "f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f" } }, + { url = "https://files.pythonhosted.org/packages/c4/5e/72db90970887bbe02296612bd61b0fa31e6d88aa24f6a4853db3e96c575e/regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-09-19T00:35:42Z, size = 781900, hashes = { sha256 = "a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a" } }, + { url = "https://files.pythonhosted.org/packages/50/ff/596be45eea8e9bc31677fde243fa2904d00aad1b32c31bce26c3dbba0b9e/regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-19T00:35:43Z, size = 852952, hashes = { sha256 = "5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9" } }, + { url = "https://files.pythonhosted.org/packages/e5/1b/2dfa348fa551e900ed3f5f63f74185b6a08e8a76bc62bc9c106f4f92668b/regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", upload-time = 2025-09-19T00:35:45Z, size = 844355, hashes = { sha256 = "fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2" } }, + { url = "https://files.pythonhosted.org/packages/f4/bf/aefb1def27fe33b8cbbb19c75c13aefccfbef1c6686f8e7f7095705969c7/regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-09-19T00:35:46Z, size = 787254, hashes = { sha256 = "f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95" } }, + { url = "https://files.pythonhosted.org/packages/e3/4e/8ef042e7cf0dbbb401e784e896acfc1b367b95dfbfc9ada94c2ed55a081f/regex-2025.9.18-cp311-cp311-win32.whl", upload-time = 2025-09-19T00:35:48Z, size = 264129, hashes = { sha256 = "895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07" } }, + { url = "https://files.pythonhosted.org/packages/b4/7d/c4fcabf80dcdd6821c0578ad9b451f8640b9110fb3dcb74793dd077069ff/regex-2025.9.18-cp311-cp311-win_amd64.whl", upload-time = 2025-09-19T00:36:00Z, size = 276160, hashes = { sha256 = "7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9" } }, + { url = "https://files.pythonhosted.org/packages/64/f8/0e13c8ae4d6df9d128afaba138342d532283d53a4c1e7a8c93d6756c8f4a/regex-2025.9.18-cp311-cp311-win_arm64.whl", upload-time = 2025-09-19T00:36:02Z, size = 268471, hashes = { sha256 = "fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df" } }, + { url = "https://files.pythonhosted.org/packages/b0/99/05859d87a66ae7098222d65748f11ef7f2dff51bfd7482a4e2256c90d72b/regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", upload-time = 2025-09-19T00:36:03Z, size = 486335, hashes = { sha256 = "436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e" } }, + { url = "https://files.pythonhosted.org/packages/97/7e/d43d4e8b978890932cf7b0957fce58c5b08c66f32698f695b0c2c24a48bf/regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2025-09-19T00:36:05Z, size = 289720, hashes = { sha256 = "c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a" } }, + { url = "https://files.pythonhosted.org/packages/bb/3b/ff80886089eb5dcf7e0d2040d9aaed539e25a94300403814bb24cc775058/regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-09-19T00:36:07Z, size = 287257, hashes = { sha256 = "e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab" } }, + { url = "https://files.pythonhosted.org/packages/ee/66/243edf49dd8720cba8d5245dd4d6adcb03a1defab7238598c0c97cf549b8/regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-19T00:36:08Z, size = 797463, hashes = { sha256 = "300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5" } }, + { url = "https://files.pythonhosted.org/packages/df/71/c9d25a1142c70432e68bb03211d4a82299cd1c1fbc41db9409a394374ef5/regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-19T00:36:10Z, size = 862670, hashes = { sha256 = "7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742" } }, + { url = "https://files.pythonhosted.org/packages/f8/8f/329b1efc3a64375a294e3a92d43372bf1a351aa418e83c21f2f01cf6ec41/regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-19T00:36:12Z, size = 910881, hashes = { sha256 = "57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425" } }, + { url = "https://files.pythonhosted.org/packages/35/9e/a91b50332a9750519320ed30ec378b74c996f6befe282cfa6bb6cea7e9fd/regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-19T00:36:13Z, size = 802011, hashes = { sha256 = "4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352" } }, + { url = "https://files.pythonhosted.org/packages/a4/1d/6be3b8d7856b6e0d7ee7f942f437d0a76e0d5622983abbb6d21e21ab9a17/regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-09-19T00:36:15Z, size = 786668, hashes = { sha256 = "5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d" } }, + { url = "https://files.pythonhosted.org/packages/cb/ce/4a60e53df58bd157c5156a1736d3636f9910bdcc271d067b32b7fcd0c3a8/regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-19T00:36:16Z, size = 856578, hashes = { sha256 = "0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56" } }, + { url = "https://files.pythonhosted.org/packages/86/e8/162c91bfe7217253afccde112868afb239f94703de6580fb235058d506a6/regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", upload-time = 2025-09-19T00:36:18Z, size = 849017, hashes = { sha256 = "40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e" } }, + { url = "https://files.pythonhosted.org/packages/35/34/42b165bc45289646ea0959a1bc7531733e90b47c56a72067adfe6b3251f6/regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-09-19T00:36:20Z, size = 788150, hashes = { sha256 = "039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282" } }, + { url = "https://files.pythonhosted.org/packages/79/5d/cdd13b1f3c53afa7191593a7ad2ee24092a5a46417725ffff7f64be8342d/regex-2025.9.18-cp312-cp312-win32.whl", upload-time = 2025-09-19T00:36:21Z, size = 264536, hashes = { sha256 = "e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459" } }, + { url = "https://files.pythonhosted.org/packages/e0/f5/4a7770c9a522e7d2dc1fa3ffc83ab2ab33b0b22b447e62cffef186805302/regex-2025.9.18-cp312-cp312-win_amd64.whl", upload-time = 2025-09-19T00:36:23Z, size = 275501, hashes = { sha256 = "3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77" } }, + { url = "https://files.pythonhosted.org/packages/df/05/9ce3e110e70d225ecbed455b966003a3afda5e58e8aec2964042363a18f4/regex-2025.9.18-cp312-cp312-win_arm64.whl", upload-time = 2025-09-19T00:36:25Z, size = 268601, hashes = { sha256 = "032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5" } }, + { url = "https://files.pythonhosted.org/packages/d2/c7/5c48206a60ce33711cf7dcaeaed10dd737733a3569dc7e1dce324dd48f30/regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", upload-time = 2025-09-19T00:36:26Z, size = 485955, hashes = { sha256 = "2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2" } }, + { url = "https://files.pythonhosted.org/packages/e9/be/74fc6bb19a3c491ec1ace943e622b5a8539068771e8705e469b2da2306a7/regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-09-19T00:36:28Z, size = 289583, hashes = { sha256 = "c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb" } }, + { url = "https://files.pythonhosted.org/packages/25/c4/9ceaa433cb5dc515765560f22a19578b95b92ff12526e5a259321c4fc1a0/regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-09-19T00:36:30Z, size = 287000, hashes = { sha256 = "1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af" } }, + { url = "https://files.pythonhosted.org/packages/7d/e6/68bc9393cb4dc68018456568c048ac035854b042bc7c33cb9b99b0680afa/regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-19T00:36:31Z, size = 797535, hashes = { sha256 = "bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29" } }, + { url = "https://files.pythonhosted.org/packages/6a/1c/ebae9032d34b78ecfe9bd4b5e6575b55351dc8513485bb92326613732b8c/regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-19T00:36:33Z, size = 862603, hashes = { sha256 = "4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f" } }, + { url = "https://files.pythonhosted.org/packages/3b/74/12332c54b3882557a4bcd2b99f8be581f5c6a43cf1660a85b460dd8ff468/regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-19T00:36:34Z, size = 910829, hashes = { sha256 = "a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68" } }, + { url = "https://files.pythonhosted.org/packages/86/70/ba42d5ed606ee275f2465bfc0e2208755b06cdabd0f4c7c4b614d51b57ab/regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-19T00:36:36Z, size = 802059, hashes = { sha256 = "92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783" } }, + { url = "https://files.pythonhosted.org/packages/da/c5/fcb017e56396a7f2f8357412638d7e2963440b131a3ca549be25774b3641/regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-09-19T00:36:38Z, size = 786781, hashes = { sha256 = "0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac" } }, + { url = "https://files.pythonhosted.org/packages/c6/ee/21c4278b973f630adfb3bcb23d09d83625f3ab1ca6e40ebdffe69901c7a1/regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-19T00:36:40Z, size = 856578, hashes = { sha256 = "a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e" } }, + { url = "https://files.pythonhosted.org/packages/87/0b/de51550dc7274324435c8f1539373ac63019b0525ad720132866fff4a16a/regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", upload-time = 2025-09-19T00:36:41Z, size = 849119, hashes = { sha256 = "1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23" } }, + { url = "https://files.pythonhosted.org/packages/60/52/383d3044fc5154d9ffe4321696ee5b2ee4833a28c29b137c22c33f41885b/regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-09-19T00:36:43Z, size = 788219, hashes = { sha256 = "d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f" } }, + { url = "https://files.pythonhosted.org/packages/20/bd/2614fc302671b7359972ea212f0e3a92df4414aaeacab054a8ce80a86073/regex-2025.9.18-cp313-cp313-win32.whl", upload-time = 2025-09-19T00:36:45Z, size = 264517, hashes = { sha256 = "3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d" } }, + { url = "https://files.pythonhosted.org/packages/07/0f/ab5c1581e6563a7bffdc1974fb2d25f05689b88e2d416525271f232b1946/regex-2025.9.18-cp313-cp313-win_amd64.whl", upload-time = 2025-09-19T00:36:46Z, size = 275481, hashes = { sha256 = "16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d" } }, + { url = "https://files.pythonhosted.org/packages/49/22/ee47672bc7958f8c5667a587c2600a4fba8b6bab6e86bd6d3e2b5f7cac42/regex-2025.9.18-cp313-cp313-win_arm64.whl", upload-time = 2025-09-19T00:36:48Z, size = 268598, hashes = { sha256 = "4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb" } }, + { url = "https://files.pythonhosted.org/packages/e8/83/6887e16a187c6226cb85d8301e47d3b73ecc4505a3a13d8da2096b44fd76/regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", upload-time = 2025-09-19T00:36:49Z, size = 489765, hashes = { sha256 = "fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2" } }, + { url = "https://files.pythonhosted.org/packages/51/c5/e2f7325301ea2916ff301c8d963ba66b1b2c1b06694191df80a9c4fea5d0/regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", upload-time = 2025-09-19T00:36:51Z, size = 291228, hashes = { sha256 = "1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3" } }, + { url = "https://files.pythonhosted.org/packages/91/60/7d229d2bc6961289e864a3a3cfebf7d0d250e2e65323a8952cbb7e22d824/regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-09-19T00:36:53Z, size = 289270, hashes = { sha256 = "13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12" } }, + { url = "https://files.pythonhosted.org/packages/3c/d7/b4f06868ee2958ff6430df89857fbf3d43014bbf35538b6ec96c2704e15d/regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-19T00:36:54Z, size = 806326, hashes = { sha256 = "874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0" } }, + { url = "https://files.pythonhosted.org/packages/d6/e4/bca99034a8f1b9b62ccf337402a8e5b959dd5ba0e5e5b2ead70273df3277/regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-19T00:36:56Z, size = 871556, hashes = { sha256 = "d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6" } }, + { url = "https://files.pythonhosted.org/packages/6d/df/e06ffaf078a162f6dd6b101a5ea9b44696dca860a48136b3ae4a9caf25e2/regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-19T00:36:57Z, size = 913817, hashes = { sha256 = "05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef" } }, + { url = "https://files.pythonhosted.org/packages/9e/05/25b05480b63292fd8e84800b1648e160ca778127b8d2367a0a258fa2e225/regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-19T00:36:59Z, size = 811055, hashes = { sha256 = "5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a" } }, + { url = "https://files.pythonhosted.org/packages/70/97/7bc7574655eb651ba3a916ed4b1be6798ae97af30104f655d8efd0cab24b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", upload-time = 2025-09-19T00:37:01Z, size = 794534, hashes = { sha256 = "65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d" } }, + { url = "https://files.pythonhosted.org/packages/b4/c2/d5da49166a52dda879855ecdba0117f073583db2b39bb47ce9a3378a8e9e/regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-19T00:37:03Z, size = 866684, hashes = { sha256 = "ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368" } }, + { url = "https://files.pythonhosted.org/packages/bd/2d/0a5c4e6ec417de56b89ff4418ecc72f7e3feca806824c75ad0bbdae0516b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", upload-time = 2025-09-19T00:37:04Z, size = 853282, hashes = { sha256 = "e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90" } }, + { url = "https://files.pythonhosted.org/packages/f4/8e/d656af63e31a86572ec829665d6fa06eae7e144771e0330650a8bb865635/regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", upload-time = 2025-09-19T00:37:06Z, size = 797830, hashes = { sha256 = "9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7" } }, + { url = "https://files.pythonhosted.org/packages/db/ce/06edc89df8f7b83ffd321b6071be4c54dc7332c0f77860edc40ce57d757b/regex-2025.9.18-cp313-cp313t-win32.whl", upload-time = 2025-09-19T00:37:08Z, size = 267281, hashes = { sha256 = "168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e" } }, + { url = "https://files.pythonhosted.org/packages/83/9a/2b5d9c8b307a451fd17068719d971d3634ca29864b89ed5c18e499446d4a/regex-2025.9.18-cp313-cp313t-win_amd64.whl", upload-time = 2025-09-19T00:37:10Z, size = 278724, hashes = { sha256 = "d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730" } }, + { url = "https://files.pythonhosted.org/packages/3d/70/177d31e8089a278a764f8ec9a3faac8d14a312d622a47385d4b43905806f/regex-2025.9.18-cp313-cp313t-win_arm64.whl", upload-time = 2025-09-19T00:37:13Z, size = 269771, hashes = { sha256 = "dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a" } }, + { url = "https://files.pythonhosted.org/packages/44/b7/3b4663aa3b4af16819f2ab6a78c4111c7e9b066725d8107753c2257448a5/regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", upload-time = 2025-09-19T00:37:14Z, size = 486130, hashes = { sha256 = "c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129" } }, + { url = "https://files.pythonhosted.org/packages/80/5b/4533f5d7ac9c6a02a4725fe8883de2aebc713e67e842c04cf02626afb747/regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", upload-time = 2025-09-19T00:37:16Z, size = 289539, hashes = { sha256 = "8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea" } }, + { url = "https://files.pythonhosted.org/packages/b8/8d/5ab6797c2750985f79e9995fad3254caa4520846580f266ae3b56d1cae58/regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-09-19T00:37:18Z, size = 287233, hashes = { sha256 = "29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1" } }, + { url = "https://files.pythonhosted.org/packages/cb/1e/95afcb02ba8d3a64e6ffeb801718ce73471ad6440c55d993f65a4a5e7a92/regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-19T00:37:19Z, size = 797876, hashes = { sha256 = "7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47" } }, + { url = "https://files.pythonhosted.org/packages/c8/fb/720b1f49cec1f3b5a9fea5b34cd22b88b5ebccc8c1b5de9cc6f65eed165a/regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-19T00:37:21Z, size = 863385, hashes = { sha256 = "5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379" } }, + { url = "https://files.pythonhosted.org/packages/a9/ca/e0d07ecf701e1616f015a720dc13b84c582024cbfbb3fc5394ae204adbd7/regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-19T00:37:23Z, size = 910220, hashes = { sha256 = "1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203" } }, + { url = "https://files.pythonhosted.org/packages/b6/45/bba86413b910b708eca705a5af62163d5d396d5f647ed9485580c7025209/regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-19T00:37:25Z, size = 801827, hashes = { sha256 = "7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164" } }, + { url = "https://files.pythonhosted.org/packages/b8/a6/740fbd9fcac31a1305a8eed30b44bf0f7f1e042342be0a4722c0365ecfca/regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-09-19T00:37:27Z, size = 786843, hashes = { sha256 = "bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb" } }, + { url = "https://files.pythonhosted.org/packages/80/a7/0579e8560682645906da640c9055506465d809cb0f5415d9976f417209a6/regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-19T00:37:29Z, size = 857430, hashes = { sha256 = "6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743" } }, + { url = "https://files.pythonhosted.org/packages/8d/9b/4dc96b6c17b38900cc9fee254fc9271d0dde044e82c78c0811b58754fde5/regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", upload-time = 2025-09-19T00:37:31Z, size = 848612, hashes = { sha256 = "ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282" } }, + { url = "https://files.pythonhosted.org/packages/b3/6a/6f659f99bebb1775e5ac81a3fb837b85897c1a4ef5acffd0ff8ffe7e67fb/regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-09-19T00:37:34Z, size = 787967, hashes = { sha256 = "d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773" } }, + { url = "https://files.pythonhosted.org/packages/61/35/9e35665f097c07cf384a6b90a1ac11b0b1693084a0b7a675b06f760496c6/regex-2025.9.18-cp314-cp314-win32.whl", upload-time = 2025-09-19T00:37:35Z, size = 269847, hashes = { sha256 = "0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788" } }, + { url = "https://files.pythonhosted.org/packages/af/64/27594dbe0f1590b82de2821ebfe9a359b44dcb9b65524876cd12fabc447b/regex-2025.9.18-cp314-cp314-win_amd64.whl", upload-time = 2025-09-19T00:37:37Z, size = 278755, hashes = { sha256 = "57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3" } }, + { url = "https://files.pythonhosted.org/packages/30/a3/0cd8d0d342886bd7d7f252d701b20ae1a3c72dc7f34ef4b2d17790280a09/regex-2025.9.18-cp314-cp314-win_arm64.whl", upload-time = 2025-09-19T00:37:39Z, size = 271873, hashes = { sha256 = "6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d" } }, + { url = "https://files.pythonhosted.org/packages/99/cb/8a1ab05ecf404e18b54348e293d9b7a60ec2bd7aa59e637020c5eea852e8/regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", upload-time = 2025-09-19T00:37:40Z, size = 489773, hashes = { sha256 = "b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306" } }, + { url = "https://files.pythonhosted.org/packages/93/3b/6543c9b7f7e734d2404fa2863d0d710c907bef99d4598760ed4563d634c3/regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", upload-time = 2025-09-19T00:37:42Z, size = 291221, hashes = { sha256 = "0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946" } }, + { url = "https://files.pythonhosted.org/packages/cd/91/e9fdee6ad6bf708d98c5d17fded423dcb0661795a49cba1b4ffb8358377a/regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-09-19T00:37:44Z, size = 289268, hashes = { sha256 = "ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f" } }, + { url = "https://files.pythonhosted.org/packages/94/a6/bc3e8a918abe4741dadeaeb6c508e3a4ea847ff36030d820d89858f96a6c/regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-19T00:37:46Z, size = 806659, hashes = { sha256 = "b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95" } }, + { url = "https://files.pythonhosted.org/packages/2b/71/ea62dbeb55d9e6905c7b5a49f75615ea1373afcad95830047e4e310db979/regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-19T00:37:48Z, size = 871701, hashes = { sha256 = "f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b" } }, + { url = "https://files.pythonhosted.org/packages/6a/90/fbe9dedb7dad24a3a4399c0bae64bfa932ec8922a0a9acf7bc88db30b161/regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-19T00:37:51Z, size = 913742, hashes = { sha256 = "0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3" } }, + { url = "https://files.pythonhosted.org/packages/f0/1c/47e4a8c0e73d41eb9eb9fdeba3b1b810110a5139a2526e82fd29c2d9f867/regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-19T00:37:52Z, size = 811117, hashes = { sha256 = "dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571" } }, + { url = "https://files.pythonhosted.org/packages/2a/da/435f29fddfd015111523671e36d30af3342e8136a889159b05c1d9110480/regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", upload-time = 2025-09-19T00:37:54Z, size = 794647, hashes = { sha256 = "48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad" } }, + { url = "https://files.pythonhosted.org/packages/23/66/df5e6dcca25c8bc57ce404eebc7342310a0d218db739d7882c9a2b5974a3/regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-19T00:37:56Z, size = 866747, hashes = { sha256 = "274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494" } }, + { url = "https://files.pythonhosted.org/packages/82/42/94392b39b531f2e469b2daa40acf454863733b674481fda17462a5ffadac/regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", upload-time = 2025-09-19T00:37:58Z, size = 853434, hashes = { sha256 = "a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b" } }, + { url = "https://files.pythonhosted.org/packages/a8/f8/dcc64c7f7bbe58842a8f89622b50c58c3598fbbf4aad0a488d6df2c699f1/regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", upload-time = 2025-09-19T00:38:00Z, size = 798024, hashes = { sha256 = "06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41" } }, + { url = "https://files.pythonhosted.org/packages/20/8d/edf1c5d5aa98f99a692313db813ec487732946784f8f93145e0153d910e5/regex-2025.9.18-cp314-cp314t-win32.whl", upload-time = 2025-09-19T00:38:02Z, size = 273029, hashes = { sha256 = "2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096" } }, + { url = "https://files.pythonhosted.org/packages/a7/24/02d4e4f88466f17b145f7ea2b2c11af3a942db6222429c2c146accf16054/regex-2025.9.18-cp314-cp314t-win_amd64.whl", upload-time = 2025-09-19T00:38:04Z, size = 282680, hashes = { sha256 = "8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a" } }, + { url = "https://files.pythonhosted.org/packages/1f/a3/c64894858aaaa454caa7cc47e2f225b04d3ed08ad649eacf58d45817fad2/regex-2025.9.18-cp314-cp314t-win_arm64.whl", upload-time = 2025-09-19T00:38:05Z, size = 273034, hashes = { sha256 = "b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01" } }, + { url = "https://files.pythonhosted.org/packages/ed/d2/5b0ded10467d6e96f78de5e6f195b7f9b57251f411b1090004597cffe5d9/regex-2025.9.18-cp39-cp39-macosx_10_9_universal2.whl", upload-time = 2025-09-19T00:38:07Z, size = 484847, hashes = { sha256 = "3dbcfcaa18e9480669030d07371713c10b4f1a41f791ffa5cb1a99f24e777f40" } }, + { url = "https://files.pythonhosted.org/packages/55/35/051da2c0ae6124e3f1aa1442ecc2bb4e2de930e95433bce1301a2e7ae255/regex-2025.9.18-cp39-cp39-macosx_10_9_x86_64.whl", upload-time = 2025-09-19T00:38:09Z, size = 288995, hashes = { sha256 = "1e85f73ef7095f0380208269055ae20524bfde3f27c5384126ddccf20382a638" } }, + { url = "https://files.pythonhosted.org/packages/22/4b/4bfc51cad95263d25b6ed8c5253831b2536e8e279e6736d0a08c9f7ffe98/regex-2025.9.18-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2025-09-19T00:38:11Z, size = 286642, hashes = { sha256 = "9098e29b3ea4ffffeade423f6779665e2a4f8db64e699c0ed737ef0db6ba7b12" } }, + { url = "https://files.pythonhosted.org/packages/0e/67/d2f3e2483e09d1e9f7d93b4fe106b04933fba5e619bc901530d1c90d62da/regex-2025.9.18-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-19T00:38:12Z, size = 779896, hashes = { sha256 = "90b6b7a2d0f45b7ecaaee1aec6b362184d6596ba2092dd583ffba1b78dd0231c" } }, + { url = "https://files.pythonhosted.org/packages/14/5e/49a4f07ce6f5563de02b0e321220b9534f3fd3bae275311b785dd618aea5/regex-2025.9.18-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2025-09-19T00:38:14Z, size = 848954, hashes = { sha256 = "c81b892af4a38286101502eae7aec69f7cd749a893d9987a92776954f3943408" } }, + { url = "https://files.pythonhosted.org/packages/00/8d/f5995ae51225c77ca9215d78ceb1dc30c52fa2b22c41dac977214e8b4bbd/regex-2025.9.18-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2025-09-19T00:38:16Z, size = 896770, hashes = { sha256 = "3b524d010973f2e1929aeb635418d468d869a5f77b52084d9f74c272189c251d" } }, + { url = "https://files.pythonhosted.org/packages/6b/15/2a3a744d73a557337c7561db2114bab10b4e9941c626c03169ea62f42c8f/regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-19T00:38:18Z, size = 789484, hashes = { sha256 = "6b498437c026a3d5d0be0020023ff76d70ae4d77118e92f6f26c9d0423452446" } }, + { url = "https://files.pythonhosted.org/packages/d8/27/e425f3d17d32062a657b836d0c8a68f5e71a9e6295fa637159f265eaa609/regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-19T00:38:19Z, size = 780150, hashes = { sha256 = "0716e4d6e58853d83f6563f3cf25c281ff46cf7107e5f11879e32cb0b59797d9" } }, + { url = "https://files.pythonhosted.org/packages/62/28/79dfae89b6fd7901b82611ac1a96ec25deceb7e918e9c5eb3f96cf5ad654/regex-2025.9.18-cp39-cp39-musllinux_1_2_aarch64.whl", upload-time = 2025-09-19T00:38:21Z, size = 773160, hashes = { sha256 = "065b6956749379d41db2625f880b637d4acc14c0a4de0d25d609a62850e96d36" } }, + { url = "https://files.pythonhosted.org/packages/0b/67/df83d6ae608f487448e9be7ac26211af2afa2b6e34465fde3e07d1f11290/regex-2025.9.18-cp39-cp39-musllinux_1_2_ppc64le.whl", upload-time = 2025-09-19T00:38:23Z, size = 843555, hashes = { sha256 = "d4a691494439287c08ddb9b5793da605ee80299dd31e95fa3f323fac3c33d9d4" } }, + { url = "https://files.pythonhosted.org/packages/32/67/c65f56f3edd3f213d3aa41e9b9b07cc2247721a23d34bcfb2947dc0f4685/regex-2025.9.18-cp39-cp39-musllinux_1_2_s390x.whl", upload-time = 2025-09-19T00:38:25Z, size = 834169, hashes = { sha256 = "ef8d10cc0989565bcbe45fb4439f044594d5c2b8919d3d229ea2c4238f1d55b0" } }, + { url = "https://files.pythonhosted.org/packages/95/90/7fca37435e3aa1a032c38fa1e171fdaf809c8dbf2717508e3f6a92c75446/regex-2025.9.18-cp39-cp39-musllinux_1_2_x86_64.whl", upload-time = 2025-09-19T00:38:28Z, size = 778024, hashes = { sha256 = "4baeb1b16735ac969a7eeecc216f1f8b7caf60431f38a2671ae601f716a32d25" } }, + { url = "https://files.pythonhosted.org/packages/8b/05/c2ee512cdf34d6be5ac5cf938a58c1b79a9d96cbad404bc4d70404212edb/regex-2025.9.18-cp39-cp39-win32.whl", upload-time = 2025-09-19T00:38:30Z, size = 264151, hashes = { sha256 = "8e5f41ad24a1e0b5dfcf4c4e5d9f5bd54c895feb5708dd0c1d0d35693b24d478" } }, + { url = "https://files.pythonhosted.org/packages/f8/2f/8414fb46181b6108484f04d670ece196db6734cc4c683f41125043fd3280/regex-2025.9.18-cp39-cp39-win_amd64.whl", upload-time = 2025-09-19T00:38:31Z, size = 276232, hashes = { sha256 = "50e8290707f2fb8e314ab3831e594da71e062f1d623b05266f8cfe4db4949afd" } }, + { url = "https://files.pythonhosted.org/packages/61/63/f40931d477e1ed4b53105d506758a58cfec1b052c12972054930ec743ee5/regex-2025.9.18-cp39-cp39-win_arm64.whl", upload-time = 2025-09-19T00:38:34Z, size = 268505, hashes = { sha256 = "039a9d7195fd88c943d7c777d4941e8ef736731947becce773c31a1009cb3c35" } }, +] + +[[packages]] name = "requests" -version = "2.32.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, -] - -[[package]] +version = "2.32.5" +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", upload-time = 2025-08-18T20:46:02Z, size = 134517, hashes = { sha256 = "dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", upload-time = 2025-08-18T20:46:00Z, size = 64738, hashes = { sha256 = "2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6" } }] + +[[packages]] +name = "ruff" +version = "0.14.0" +sdist = { url = "https://files.pythonhosted.org/packages/41/b9/9bd84453ed6dd04688de9b3f3a4146a1698e8faae2ceeccce4e14c67ae17/ruff-0.14.0.tar.gz", upload-time = 2025-10-07T18:21:55Z, size = 5452071, hashes = { sha256 = "62ec8969b7510f77945df916de15da55311fade8d6050995ff7f680afe582c57" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/4e/79d463a5f80654e93fa653ebfb98e0becc3f0e7cf6219c9ddedf1e197072/ruff-0.14.0-py3-none-linux_armv6l.whl", upload-time = 2025-10-07T18:21:00Z, size = 12494532, hashes = { sha256 = "58e15bffa7054299becf4bab8a1187062c6f8cafbe9f6e39e0d5aface455d6b3" } }, + { url = "https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl", upload-time = 2025-10-07T18:21:04Z, size = 13160768, hashes = { sha256 = "838d1b065f4df676b7c9957992f2304e41ead7a50a568185efd404297d5701e8" } }, + { url = "https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl", upload-time = 2025-10-07T18:21:07Z, size = 12363376, hashes = { sha256 = "703799d059ba50f745605b04638fa7e9682cc3da084b2092feee63500ff3d9b8" } }, + { url = "https://files.pythonhosted.org/packages/42/e2/1ffef5a1875add82416ff388fcb7ea8b22a53be67a638487937aea81af27/ruff-0.14.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-07T18:21:10Z, size = 12608055, hashes = { sha256 = "3ba9a8925e90f861502f7d974cc60e18ca29c72bb0ee8bfeabb6ade35a3abde7" } }, + { url = "https://files.pythonhosted.org/packages/4a/32/986725199d7cee510d9f1dfdf95bf1efc5fa9dd714d0d85c1fb1f6be3bc3/ruff-0.14.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-07T18:21:13Z, size = 12318544, hashes = { sha256 = "e41f785498bd200ffc276eb9e1570c019c1d907b07cfb081092c8ad51975bbe7" } }, + { url = "https://files.pythonhosted.org/packages/9a/ed/4969cefd53315164c94eaf4da7cfba1f267dc275b0abdd593d11c90829a3/ruff-0.14.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-10-07T18:21:16Z, size = 14001280, hashes = { sha256 = "30a58c087aef4584c193aebf2700f0fbcfc1e77b89c7385e3139956fa90434e2" } }, + { url = "https://files.pythonhosted.org/packages/ab/ad/96c1fc9f8854c37681c9613d825925c7f24ca1acfc62a4eb3896b50bacd2/ruff-0.14.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", upload-time = 2025-10-07T18:21:19Z, size = 15027286, hashes = { sha256 = "f8d07350bc7af0a5ce8812b7d5c1a7293cf02476752f23fdfc500d24b79b783c" } }, + { url = "https://files.pythonhosted.org/packages/b3/00/1426978f97df4fe331074baf69615f579dc4e7c37bb4c6f57c2aad80c87f/ruff-0.14.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-07T18:21:22Z, size = 14451506, hashes = { sha256 = "eec3bbbf3a7d5482b5c1f42d5fc972774d71d107d447919fca620b0be3e3b75e" } }, + { url = "https://files.pythonhosted.org/packages/58/d5/9c1cea6e493c0cf0647674cca26b579ea9d2a213b74b5c195fbeb9678e15/ruff-0.14.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-07T18:21:25Z, size = 13437384, hashes = { sha256 = "16b68e183a0e28e5c176d51004aaa40559e8f90065a10a559176713fcf435206" } }, + { url = "https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-07T18:21:28Z, size = 13447976, hashes = { sha256 = "eb732d17db2e945cfcbbc52af0143eda1da36ca8ae25083dd4f66f1542fdf82e" } }, + { url = "https://files.pythonhosted.org/packages/3b/c0/ac42f546d07e4f49f62332576cb845d45c67cf5610d1851254e341d563b6/ruff-0.14.0-py3-none-manylinux_2_31_riscv64.whl", upload-time = 2025-10-07T18:21:31Z, size = 13682850, hashes = { sha256 = "c958f66ab884b7873e72df38dcabee03d556a8f2ee1b8538ee1c2bbd619883dd" } }, + { url = "https://files.pythonhosted.org/packages/5f/c4/4b0c9bcadd45b4c29fe1af9c5d1dc0ca87b4021665dfbe1c4688d407aa20/ruff-0.14.0-py3-none-musllinux_1_2_aarch64.whl", upload-time = 2025-10-07T18:21:35Z, size = 12449825, hashes = { sha256 = "7eb0499a2e01f6e0c285afc5bac43ab380cbfc17cd43a2e1dd10ec97d6f2c42d" } }, + { url = "https://files.pythonhosted.org/packages/4b/a8/e2e76288e6c16540fa820d148d83e55f15e994d852485f221b9524514730/ruff-0.14.0-py3-none-musllinux_1_2_armv7l.whl", upload-time = 2025-10-07T18:21:38Z, size = 12272599, hashes = { sha256 = "4c63b2d99fafa05efca0ab198fd48fa6030d57e4423df3f18e03aa62518c565f" } }, + { url = "https://files.pythonhosted.org/packages/18/14/e2815d8eff847391af632b22422b8207704222ff575dec8d044f9ab779b2/ruff-0.14.0-py3-none-musllinux_1_2_i686.whl", upload-time = 2025-10-07T18:21:41Z, size = 13193828, hashes = { sha256 = "668fce701b7a222f3f5327f86909db2bbe99c30877c8001ff934c5413812ac02" } }, + { url = "https://files.pythonhosted.org/packages/44/c6/61ccc2987cf0aecc588ff8f3212dea64840770e60d78f5606cd7dc34de32/ruff-0.14.0-py3-none-musllinux_1_2_x86_64.whl", upload-time = 2025-10-07T18:21:44Z, size = 13628617, hashes = { sha256 = "a86bf575e05cb68dcb34e4c7dfe1064d44d3f0c04bbc0491949092192b515296" } }, + { url = "https://files.pythonhosted.org/packages/73/e6/03b882225a1b0627e75339b420883dc3c90707a8917d2284abef7a58d317/ruff-0.14.0-py3-none-win32.whl", upload-time = 2025-10-07T18:21:46Z, size = 12367872, hashes = { sha256 = "7450a243d7125d1c032cb4b93d9625dea46c8c42b4f06c6b709baac168e10543" } }, + { url = "https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl", upload-time = 2025-10-07T18:21:50Z, size = 13464628, hashes = { sha256 = "ea95da28cd874c4d9c922b39381cbd69cb7e7b49c21b8152b014bd4f52acddc2" } }, + { url = "https://files.pythonhosted.org/packages/c6/2a/65880dfd0e13f7f13a775998f34703674a4554906167dce02daf7865b954/ruff-0.14.0-py3-none-win_arm64.whl", upload-time = 2025-10-07T18:21:53Z, size = 12565142, hashes = { sha256 = "f42c9495f5c13ff841b1da4cb3c2a42075409592825dada7c5885c2c844ac730" } }, +] + +[[packages]] name = "safetensors" -version = "0.5.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/7e/2d5d6ee7b40c0682315367ec7475693d110f512922d582fef1bd4a63adc3/safetensors-0.5.3.tar.gz", hash = "sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965", size = 67210 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/ae/88f6c49dbd0cc4da0e08610019a3c78a7d390879a919411a410a1876d03a/safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073", size = 436917 }, - { url = "https://files.pythonhosted.org/packages/b8/3b/11f1b4a2f5d2ab7da34ecc062b0bc301f2be024d110a6466726bec8c055c/safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7", size = 418419 }, - { url = "https://files.pythonhosted.org/packages/5d/9a/add3e6fef267658075c5a41573c26d42d80c935cdc992384dfae435feaef/safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467", size = 459493 }, - { url = "https://files.pythonhosted.org/packages/df/5c/bf2cae92222513cc23b3ff85c4a1bb2811a2c3583ac0f8e8d502751de934/safetensors-0.5.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e", size = 472400 }, - { url = "https://files.pythonhosted.org/packages/58/11/7456afb740bd45782d0f4c8e8e1bb9e572f1bf82899fb6ace58af47b4282/safetensors-0.5.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d", size = 522891 }, - { url = "https://files.pythonhosted.org/packages/57/3d/fe73a9d2ace487e7285f6e157afee2383bd1ddb911b7cb44a55cf812eae3/safetensors-0.5.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9", size = 537694 }, - { url = "https://files.pythonhosted.org/packages/a6/f8/dae3421624fcc87a89d42e1898a798bc7ff72c61f38973a65d60df8f124c/safetensors-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a", size = 471642 }, - { url = "https://files.pythonhosted.org/packages/ce/20/1fbe16f9b815f6c5a672f5b760951e20e17e43f67f231428f871909a37f6/safetensors-0.5.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d", size = 502241 }, - { url = "https://files.pythonhosted.org/packages/5f/18/8e108846b506487aa4629fe4116b27db65c3dde922de2c8e0cc1133f3f29/safetensors-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b", size = 638001 }, - { url = "https://files.pythonhosted.org/packages/82/5a/c116111d8291af6c8c8a8b40628fe833b9db97d8141c2a82359d14d9e078/safetensors-0.5.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff", size = 734013 }, - { url = "https://files.pythonhosted.org/packages/7d/ff/41fcc4d3b7de837963622e8610d998710705bbde9a8a17221d85e5d0baad/safetensors-0.5.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135", size = 670687 }, - { url = "https://files.pythonhosted.org/packages/40/ad/2b113098e69c985a3d8fbda4b902778eae4a35b7d5188859b4a63d30c161/safetensors-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04", size = 643147 }, - { url = "https://files.pythonhosted.org/packages/0a/0c/95aeb51d4246bd9a3242d3d8349c1112b4ee7611a4b40f0c5c93b05f001d/safetensors-0.5.3-cp38-abi3-win32.whl", hash = "sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace", size = 296677 }, - { url = "https://files.pythonhosted.org/packages/69/e2/b011c38e5394c4c18fb5500778a55ec43ad6106126e74723ffaee246f56e/safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11", size = 308878 }, -] - -[[package]] +version = "0.6.2" +sdist = { url = "https://files.pythonhosted.org/packages/ac/cc/738f3011628920e027a11754d9cae9abec1aed00f7ae860abbf843755233/safetensors-0.6.2.tar.gz", upload-time = 2025-08-08T13:13:58Z, size = 197968, hashes = { sha256 = "43ff2aa0e6fa2dc3ea5524ac7ad93a9839256b8703761e76e2d0b2a3fa4f15d9" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/b1/3f5fd73c039fc87dba3ff8b5d528bfc5a32b597fea8e7a6a4800343a17c7/safetensors-0.6.2-cp38-abi3-macosx_10_12_x86_64.whl", upload-time = 2025-08-08T13:13:52Z, size = 454797, hashes = { sha256 = "9c85ede8ec58f120bad982ec47746981e210492a6db876882aa021446af8ffba" } }, + { url = "https://files.pythonhosted.org/packages/8c/c9/bb114c158540ee17907ec470d01980957fdaf87b4aa07914c24eba87b9c6/safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl", upload-time = 2025-08-08T13:13:50Z, size = 432206, hashes = { sha256 = "d6675cf4b39c98dbd7d940598028f3742e0375a6b4d4277e76beb0c35f4b843b" } }, + { url = "https://files.pythonhosted.org/packages/d3/8e/f70c34e47df3110e8e0bb268d90db8d4be8958a54ab0336c9be4fe86dac8/safetensors-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-08-08T13:13:41Z, size = 473261, hashes = { sha256 = "1d2d2b3ce1e2509c68932ca03ab8f20570920cd9754b05063d4368ee52833ecd" } }, + { url = "https://files.pythonhosted.org/packages/2a/f5/be9c6a7c7ef773e1996dc214e73485286df1836dbd063e8085ee1976f9cb/safetensors-0.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-08-08T13:13:43Z, size = 485117, hashes = { sha256 = "93de35a18f46b0f5a6a1f9e26d91b442094f2df02e9fd7acf224cfec4238821a" } }, + { url = "https://files.pythonhosted.org/packages/c9/55/23f2d0a2c96ed8665bf17a30ab4ce5270413f4d74b6d87dd663258b9af31/safetensors-0.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-08-08T13:13:45Z, size = 616154, hashes = { sha256 = "89a89b505f335640f9120fac65ddeb83e40f1fd081cb8ed88b505bdccec8d0a1" } }, + { url = "https://files.pythonhosted.org/packages/98/c6/affb0bd9ce02aa46e7acddbe087912a04d953d7a4d74b708c91b5806ef3f/safetensors-0.6.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-08-08T13:13:46Z, size = 520713, hashes = { sha256 = "fc4d0d0b937e04bdf2ae6f70cd3ad51328635fe0e6214aa1fc811f3b576b3bda" } }, + { url = "https://files.pythonhosted.org/packages/fe/5d/5a514d7b88e310c8b146e2404e0dc161282e78634d9358975fd56dfd14be/safetensors-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-08-08T13:13:49Z, size = 485835, hashes = { sha256 = "8045db2c872db8f4cbe3faa0495932d89c38c899c603f21e9b6486951a5ecb8f" } }, + { url = "https://files.pythonhosted.org/packages/7a/7b/4fc3b2ba62c352b2071bea9cfbad330fadda70579f617506ae1a2f129cab/safetensors-0.6.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", upload-time = 2025-08-08T13:13:47Z, size = 521503, hashes = { sha256 = "81e67e8bab9878bb568cffbc5f5e655adb38d2418351dc0859ccac158f753e19" } }, + { url = "https://files.pythonhosted.org/packages/5a/50/0057e11fe1f3cead9254315a6c106a16dd4b1a19cd247f7cc6414f6b7866/safetensors-0.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", upload-time = 2025-08-08T13:13:53Z, size = 652256, hashes = { sha256 = "b0e4d029ab0a0e0e4fdf142b194514695b1d7d3735503ba700cf36d0fc7136ce" } }, + { url = "https://files.pythonhosted.org/packages/e9/29/473f789e4ac242593ac1656fbece6e1ecd860bb289e635e963667807afe3/safetensors-0.6.2-cp38-abi3-musllinux_1_2_armv7l.whl", upload-time = 2025-08-08T13:13:54Z, size = 747281, hashes = { sha256 = "fa48268185c52bfe8771e46325a1e21d317207bcabcb72e65c6e28e9ffeb29c7" } }, + { url = "https://files.pythonhosted.org/packages/68/52/f7324aad7f2df99e05525c84d352dc217e0fa637a4f603e9f2eedfbe2c67/safetensors-0.6.2-cp38-abi3-musllinux_1_2_i686.whl", upload-time = 2025-08-08T13:13:55Z, size = 692286, hashes = { sha256 = "d83c20c12c2d2f465997c51b7ecb00e407e5f94d7dec3ea0cc11d86f60d3fde5" } }, + { url = "https://files.pythonhosted.org/packages/ad/fe/cad1d9762868c7c5dc70c8620074df28ebb1a8e4c17d4c0cb031889c457e/safetensors-0.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", upload-time = 2025-08-08T13:13:57Z, size = 655957, hashes = { sha256 = "d944cea65fad0ead848b6ec2c37cc0b197194bec228f8020054742190e9312ac" } }, + { url = "https://files.pythonhosted.org/packages/59/a7/e2158e17bbe57d104f0abbd95dff60dda916cf277c9f9663b4bf9bad8b6e/safetensors-0.6.2-cp38-abi3-win32.whl", upload-time = 2025-08-08T13:14:01Z, size = 308926, hashes = { sha256 = "cab75ca7c064d3911411461151cb69380c9225798a20e712b102edda2542ddb1" } }, + { url = "https://files.pythonhosted.org/packages/2c/c3/c0be1135726618dc1e28d181b8c442403d8dbb9e273fd791de2d4384bcdd/safetensors-0.6.2-cp38-abi3-win_amd64.whl", upload-time = 2025-08-08T13:13:59Z, size = 320192, hashes = { sha256 = "c7b214870df923cbc1593c3faee16bec59ea462758699bd3fee399d00aac072c" } }, +] + +[[packages]] name = "scikit-learn" -version = "1.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "joblib" }, - { name = "numpy" }, - { name = "scipy" }, - { name = "threadpoolctl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9e/a5/4ae3b3a0755f7b35a280ac90b28817d1f380318973cff14075ab41ef50d9/scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e", size = 7068312 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/59/8eb1872ca87009bdcdb7f3cdc679ad557b992c12f4b61f9250659e592c63/scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322", size = 12010001 }, - { url = "https://files.pythonhosted.org/packages/9d/05/f2fc4effc5b32e525408524c982c468c29d22f828834f0625c5ef3d601be/scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1", size = 11096360 }, - { url = "https://files.pythonhosted.org/packages/c8/e4/4195d52cf4f113573fb8ebc44ed5a81bd511a92c0228889125fac2f4c3d1/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348", size = 12209004 }, - { url = "https://files.pythonhosted.org/packages/94/be/47e16cdd1e7fcf97d95b3cb08bde1abb13e627861af427a3651fcb80b517/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97", size = 13171776 }, - { url = "https://files.pythonhosted.org/packages/34/b0/ca92b90859070a1487827dbc672f998da95ce83edce1270fc23f96f1f61a/scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb", size = 11071865 }, - { url = "https://files.pythonhosted.org/packages/12/ae/993b0fb24a356e71e9a894e42b8a9eec528d4c70217353a1cd7a48bc25d4/scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236", size = 11955804 }, - { url = "https://files.pythonhosted.org/packages/d6/54/32fa2ee591af44507eac86406fa6bba968d1eb22831494470d0a2e4a1eb1/scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35", size = 11100530 }, - { url = "https://files.pythonhosted.org/packages/3f/58/55856da1adec655bdce77b502e94a267bf40a8c0b89f8622837f89503b5a/scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691", size = 12433852 }, - { url = "https://files.pythonhosted.org/packages/ff/4f/c83853af13901a574f8f13b645467285a48940f185b690936bb700a50863/scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f", size = 11337256 }, -] - -[[package]] +version = "1.7.2" +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", upload-time = 2025-09-09T08:21:29Z, size = 7193136, hashes = { sha256 = "20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/3e/daed796fd69cce768b8788401cc464ea90b306fb196ae1ffed0b98182859/scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", upload-time = 2025-09-09T08:20:19Z, size = 9336221, hashes = { sha256 = "6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f" } }, + { url = "https://files.pythonhosted.org/packages/1c/ce/af9d99533b24c55ff4e18d9b7b4d9919bbc6cd8f22fe7a7be01519a347d5/scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", upload-time = 2025-09-09T08:20:22Z, size = 8653834, hashes = { sha256 = "36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c" } }, + { url = "https://files.pythonhosted.org/packages/58/0e/8c2a03d518fb6bd0b6b0d4b114c63d5f1db01ff0f9925d8eb10960d01c01/scikit_learn-1.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-09T08:20:24Z, size = 9660938, hashes = { sha256 = "7a58814265dfc52b3295b1900cfb5701589d30a8bb026c7540f1e9d3499d5ec8" } }, + { url = "https://files.pythonhosted.org/packages/2b/75/4311605069b5d220e7cf5adabb38535bd96f0079313cdbb04b291479b22a/scikit_learn-1.7.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-09T08:20:26Z, size = 9477818, hashes = { sha256 = "4a847fea807e278f821a0406ca01e387f97653e284ecbd9750e3ee7c90347f18" } }, + { url = "https://files.pythonhosted.org/packages/7f/9b/87961813c34adbca21a6b3f6b2bea344c43b30217a6d24cc437c6147f3e8/scikit_learn-1.7.2-cp310-cp310-win_amd64.whl", upload-time = 2025-09-09T08:20:29Z, size = 8886969, hashes = { sha256 = "ca250e6836d10e6f402436d6463d6c0e4d8e0234cfb6a9a47835bd392b852ce5" } }, + { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", upload-time = 2025-09-09T08:20:32Z, size = 9331967, hashes = { sha256 = "c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e" } }, + { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", upload-time = 2025-09-09T08:20:34Z, size = 8648645, hashes = { sha256 = "0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1" } }, + { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-09T08:20:36Z, size = 9715424, hashes = { sha256 = "89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d" } }, + { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-09T08:20:38Z, size = 9509234, hashes = { sha256 = "8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1" } }, + { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", upload-time = 2025-09-09T08:20:41Z, size = 8894244, hashes = { sha256 = "9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1" } }, + { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2025-09-09T08:20:43Z, size = 9259818, hashes = { sha256 = "8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96" } }, + { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", upload-time = 2025-09-09T08:20:45Z, size = 8636997, hashes = { sha256 = "acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476" } }, + { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-09T08:20:47Z, size = 9478381, hashes = { sha256 = "e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b" } }, + { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-09T08:20:50Z, size = 9300296, hashes = { sha256 = "b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44" } }, + { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", upload-time = 2025-09-09T08:20:52Z, size = 8731256, hashes = { sha256 = "6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290" } }, + { url = "https://files.pythonhosted.org/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-09-09T08:20:54Z, size = 9212382, hashes = { sha256 = "0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7" } }, + { url = "https://files.pythonhosted.org/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", upload-time = 2025-09-09T08:20:57Z, size = 8592042, hashes = { sha256 = "abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe" } }, + { url = "https://files.pythonhosted.org/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-09T08:20:59Z, size = 9434180, hashes = { sha256 = "502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f" } }, + { url = "https://files.pythonhosted.org/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-09T08:21:01Z, size = 9283660, hashes = { sha256 = "7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0" } }, + { url = "https://files.pythonhosted.org/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", upload-time = 2025-09-09T08:21:04Z, size = 8702057, hashes = { sha256 = "63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c" } }, + { url = "https://files.pythonhosted.org/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", upload-time = 2025-09-09T08:21:06Z, size = 9558731, hashes = { sha256 = "9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8" } }, + { url = "https://files.pythonhosted.org/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", upload-time = 2025-09-09T08:21:08Z, size = 9038852, hashes = { sha256 = "2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a" } }, + { url = "https://files.pythonhosted.org/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-09T08:21:11Z, size = 9527094, hashes = { sha256 = "98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c" } }, + { url = "https://files.pythonhosted.org/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-09T08:21:13Z, size = 9367436, hashes = { sha256 = "191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c" } }, + { url = "https://files.pythonhosted.org/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", upload-time = 2025-09-09T08:21:15Z, size = 9275749, hashes = { sha256 = "57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973" } }, + { url = "https://files.pythonhosted.org/packages/d9/82/dee5acf66837852e8e68df6d8d3a6cb22d3df997b733b032f513d95205b7/scikit_learn-1.7.2-cp314-cp314-macosx_10_13_x86_64.whl", upload-time = 2025-09-09T08:21:18Z, size = 9208906, hashes = { sha256 = "fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33" } }, + { url = "https://files.pythonhosted.org/packages/3c/30/9029e54e17b87cb7d50d51a5926429c683d5b4c1732f0507a6c3bed9bf65/scikit_learn-1.7.2-cp314-cp314-macosx_12_0_arm64.whl", upload-time = 2025-09-09T08:21:20Z, size = 8627836, hashes = { sha256 = "f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615" } }, + { url = "https://files.pythonhosted.org/packages/60/18/4a52c635c71b536879f4b971c2cedf32c35ee78f48367885ed8025d1f7ee/scikit_learn-1.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-09T08:21:22Z, size = 9426236, hashes = { sha256 = "9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106" } }, + { url = "https://files.pythonhosted.org/packages/99/7e/290362f6ab582128c53445458a5befd471ed1ea37953d5bcf80604619250/scikit_learn-1.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-09T08:21:24Z, size = 9312593, hashes = { sha256 = "96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61" } }, + { url = "https://files.pythonhosted.org/packages/8e/87/24f541b6d62b1794939ae6422f8023703bbf6900378b2b34e0b4384dfefd/scikit_learn-1.7.2-cp314-cp314-win_amd64.whl", upload-time = 2025-09-09T08:21:26Z, size = 8820007, hashes = { sha256 = "bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8" } }, +] + +[[packages]] name = "scipy" -version = "1.15.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256 }, - { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540 }, - { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115 }, - { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884 }, - { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018 }, - { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716 }, - { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342 }, - { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869 }, - { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851 }, - { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011 }, - { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407 }, - { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030 }, - { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709 }, - { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045 }, - { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062 }, - { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132 }, - { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503 }, - { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097 }, -] - -[[package]] +version = "1.16.2" +sdist = { url = "https://files.pythonhosted.org/packages/4c/3b/546a6f0bfe791bbb7f8d591613454d15097e53f906308ec6f7c1ce588e8e/scipy-1.16.2.tar.gz", upload-time = 2025-09-11T17:48:08Z, size = 30580599, hashes = { sha256 = "af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/ef/37ed4b213d64b48422df92560af7300e10fe30b5d665dd79932baebee0c6/scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl", upload-time = 2025-09-11T17:39:20Z, size = 36619956, hashes = { sha256 = "6ab88ea43a57da1af33292ebd04b417e8e2eaf9d5aa05700be8d6e1b6501cd92" } }, + { url = "https://files.pythonhosted.org/packages/85/ab/5c2eba89b9416961a982346a4d6a647d78c91ec96ab94ed522b3b6baf444/scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl", upload-time = 2025-09-11T17:39:29Z, size = 28931117, hashes = { sha256 = "c95e96c7305c96ede73a7389f46ccd6c659c4da5ef1b2789466baeaed3622b6e" } }, + { url = "https://files.pythonhosted.org/packages/80/d1/eed51ab64d227fe60229a2d57fb60ca5898cfa50ba27d4f573e9e5f0b430/scipy-1.16.2-cp311-cp311-macosx_14_0_arm64.whl", upload-time = 2025-09-11T17:39:34Z, size = 20921997, hashes = { sha256 = "87eb178db04ece7c698220d523c170125dbffebb7af0345e66c3554f6f60c173" } }, + { url = "https://files.pythonhosted.org/packages/be/7c/33ea3e23bbadde96726edba6bf9111fb1969d14d9d477ffa202c67bec9da/scipy-1.16.2-cp311-cp311-macosx_14_0_x86_64.whl", upload-time = 2025-09-11T17:39:40Z, size = 23523374, hashes = { sha256 = "4e409eac067dcee96a57fbcf424c13f428037827ec7ee3cb671ff525ca4fc34d" } }, + { url = "https://files.pythonhosted.org/packages/96/0b/7399dc96e1e3f9a05e258c98d716196a34f528eef2ec55aad651ed136d03/scipy-1.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-11T17:39:49Z, size = 33583702, hashes = { sha256 = "e574be127bb760f0dad24ff6e217c80213d153058372362ccb9555a10fc5e8d2" } }, + { url = "https://files.pythonhosted.org/packages/1a/bc/a5c75095089b96ea72c1bd37a4497c24b581ec73db4ef58ebee142ad2d14/scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-11T17:39:57Z, size = 35883427, hashes = { sha256 = "f5db5ba6188d698ba7abab982ad6973265b74bb40a1efe1821b58c87f73892b9" } }, + { url = "https://files.pythonhosted.org/packages/ab/66/e25705ca3d2b87b97fe0a278a24b7f477b4023a926847935a1a71488a6a6/scipy-1.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-09-11T17:40:06Z, size = 36212940, hashes = { sha256 = "ec6e74c4e884104ae006d34110677bfe0098203a3fec2f3faf349f4cb05165e3" } }, + { url = "https://files.pythonhosted.org/packages/d6/fd/0bb911585e12f3abdd603d721d83fc1c7492835e1401a0e6d498d7822b4b/scipy-1.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-09-11T17:40:15Z, size = 38865092, hashes = { sha256 = "912f46667d2d3834bc3d57361f854226475f695eb08c08a904aadb1c936b6a88" } }, + { url = "https://files.pythonhosted.org/packages/d6/73/c449a7d56ba6e6f874183759f8483cde21f900a8be117d67ffbb670c2958/scipy-1.16.2-cp311-cp311-win_amd64.whl", upload-time = 2025-09-11T17:40:24Z, size = 38687626, hashes = { sha256 = "91e9e8a37befa5a69e9cacbe0bcb79ae5afb4a0b130fd6db6ee6cc0d491695fa" } }, + { url = "https://files.pythonhosted.org/packages/68/72/02f37316adf95307f5d9e579023c6899f89ff3a051fa079dbd6faafc48e5/scipy-1.16.2-cp311-cp311-win_arm64.whl", upload-time = 2025-09-11T17:40:30Z, size = 25503506, hashes = { sha256 = "f3bf75a6dcecab62afde4d1f973f1692be013110cad5338007927db8da73249c" } }, + { url = "https://files.pythonhosted.org/packages/b7/8d/6396e00db1282279a4ddd507c5f5e11f606812b608ee58517ce8abbf883f/scipy-1.16.2-cp312-cp312-macosx_10_14_x86_64.whl", upload-time = 2025-09-11T17:40:39Z, size = 36646259, hashes = { sha256 = "89d6c100fa5c48472047632e06f0876b3c4931aac1f4291afc81a3644316bb0d" } }, + { url = "https://files.pythonhosted.org/packages/3b/93/ea9edd7e193fceb8eef149804491890bde73fb169c896b61aa3e2d1e4e77/scipy-1.16.2-cp312-cp312-macosx_12_0_arm64.whl", upload-time = 2025-09-11T17:40:46Z, size = 28888976, hashes = { sha256 = "ca748936cd579d3f01928b30a17dc474550b01272d8046e3e1ee593f23620371" } }, + { url = "https://files.pythonhosted.org/packages/91/4d/281fddc3d80fd738ba86fd3aed9202331180b01e2c78eaae0642f22f7e83/scipy-1.16.2-cp312-cp312-macosx_14_0_arm64.whl", upload-time = 2025-09-11T17:40:52Z, size = 20879905, hashes = { sha256 = "fac4f8ce2ddb40e2e3d0f7ec36d2a1e7f92559a2471e59aec37bd8d9de01fec0" } }, + { url = "https://files.pythonhosted.org/packages/69/40/b33b74c84606fd301b2915f0062e45733c6ff5708d121dd0deaa8871e2d0/scipy-1.16.2-cp312-cp312-macosx_14_0_x86_64.whl", upload-time = 2025-09-11T17:40:59Z, size = 23553066, hashes = { sha256 = "033570f1dcefd79547a88e18bccacff025c8c647a330381064f561d43b821232" } }, + { url = "https://files.pythonhosted.org/packages/55/a7/22c739e2f21a42cc8f16bc76b47cff4ed54fbe0962832c589591c2abec34/scipy-1.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-11T17:41:06Z, size = 33336407, hashes = { sha256 = "ea3421209bf00c8a5ef2227de496601087d8f638a2363ee09af059bd70976dc1" } }, + { url = "https://files.pythonhosted.org/packages/53/11/a0160990b82999b45874dc60c0c183d3a3a969a563fffc476d5a9995c407/scipy-1.16.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-11T17:41:15Z, size = 35673281, hashes = { sha256 = "f66bd07ba6f84cd4a380b41d1bf3c59ea488b590a2ff96744845163309ee8e2f" } }, + { url = "https://files.pythonhosted.org/packages/96/53/7ef48a4cfcf243c3d0f1643f5887c81f29fdf76911c4e49331828e19fc0a/scipy-1.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-09-11T17:41:23Z, size = 36004222, hashes = { sha256 = "5e9feab931bd2aea4a23388c962df6468af3d808ddf2d40f94a81c5dc38f32ef" } }, + { url = "https://files.pythonhosted.org/packages/49/7f/71a69e0afd460049d41c65c630c919c537815277dfea214031005f474d78/scipy-1.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-09-11T17:41:31Z, size = 38664586, hashes = { sha256 = "03dfc75e52f72cf23ec2ced468645321407faad8f0fe7b1f5b49264adbc29cb1" } }, + { url = "https://files.pythonhosted.org/packages/34/95/20e02ca66fb495a95fba0642fd48e0c390d0ece9b9b14c6e931a60a12dea/scipy-1.16.2-cp312-cp312-win_amd64.whl", upload-time = 2025-09-11T17:41:36Z, size = 38550641, hashes = { sha256 = "0ce54e07bbb394b417457409a64fd015be623f36e330ac49306433ffe04bc97e" } }, + { url = "https://files.pythonhosted.org/packages/92/ad/13646b9beb0a95528ca46d52b7babafbe115017814a611f2065ee4e61d20/scipy-1.16.2-cp312-cp312-win_arm64.whl", upload-time = 2025-09-11T17:41:41Z, size = 25456070, hashes = { sha256 = "2a8ffaa4ac0df81a0b94577b18ee079f13fecdb924df3328fc44a7dc5ac46851" } }, + { url = "https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl", upload-time = 2025-09-11T17:41:47Z, size = 36604856, hashes = { sha256 = "84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70" } }, + { url = "https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl", upload-time = 2025-09-11T17:41:52Z, size = 28864626, hashes = { sha256 = "5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9" } }, + { url = "https://files.pythonhosted.org/packages/4c/fc/ea36098df653cca26062a627c1a94b0de659e97127c8491e18713ca0e3b9/scipy-1.16.2-cp313-cp313-macosx_14_0_arm64.whl", upload-time = 2025-09-11T17:41:57Z, size = 20855689, hashes = { sha256 = "e52729ffd45b68777c5319560014d6fd251294200625d9d70fd8626516fc49f5" } }, + { url = "https://files.pythonhosted.org/packages/dc/6f/d0b53be55727f3e6d7c72687ec18ea6d0047cf95f1f77488b99a2bafaee1/scipy-1.16.2-cp313-cp313-macosx_14_0_x86_64.whl", upload-time = 2025-09-11T17:42:02Z, size = 23512151, hashes = { sha256 = "024dd4a118cccec09ca3209b7e8e614931a6ffb804b2a601839499cb88bdf925" } }, + { url = "https://files.pythonhosted.org/packages/11/85/bf7dab56e5c4b1d3d8eef92ca8ede788418ad38a7dc3ff50262f00808760/scipy-1.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-11T17:42:07Z, size = 33329824, hashes = { sha256 = "7a5dc7ee9c33019973a470556081b0fd3c9f4c44019191039f9769183141a4d9" } }, + { url = "https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-11T17:42:13Z, size = 35681881, hashes = { sha256 = "c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7" } }, + { url = "https://files.pythonhosted.org/packages/c1/5f/331148ea5780b4fcc7007a4a6a6ee0a0c1507a796365cc642d4d226e1c3a/scipy-1.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-09-11T17:42:18Z, size = 36006219, hashes = { sha256 = "af80196eaa84f033e48444d2e0786ec47d328ba00c71e4299b602235ffef9acb" } }, + { url = "https://files.pythonhosted.org/packages/46/3a/e991aa9d2aec723b4a8dcfbfc8365edec5d5e5f9f133888067f1cbb7dfc1/scipy-1.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-09-11T17:42:25Z, size = 38682147, hashes = { sha256 = "9fb1eb735fe3d6ed1f89918224e3385fbf6f9e23757cacc35f9c78d3b712dd6e" } }, + { url = "https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl", upload-time = 2025-09-11T17:43:25Z, size = 38520766, hashes = { sha256 = "fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c" } }, + { url = "https://files.pythonhosted.org/packages/1b/a5/85d3e867b6822d331e26c862a91375bb7746a0b458db5effa093d34cdb89/scipy-1.16.2-cp313-cp313-win_arm64.whl", upload-time = 2025-09-11T17:43:30Z, size = 25451169, hashes = { sha256 = "2f5350da923ccfd0b00e07c3e5cfb316c1c0d6c1d864c07a72d092e9f20db104" } }, + { url = "https://files.pythonhosted.org/packages/09/d9/60679189bcebda55992d1a45498de6d080dcaf21ce0c8f24f888117e0c2d/scipy-1.16.2-cp313-cp313t-macosx_10_14_x86_64.whl", upload-time = 2025-09-11T17:42:30Z, size = 37012682, hashes = { sha256 = "53d8d2ee29b925344c13bda64ab51785f016b1b9617849dac10897f0701b20c1" } }, + { url = "https://files.pythonhosted.org/packages/83/be/a99d13ee4d3b7887a96f8c71361b9659ba4ef34da0338f14891e102a127f/scipy-1.16.2-cp313-cp313t-macosx_12_0_arm64.whl", upload-time = 2025-09-11T17:42:35Z, size = 29389926, hashes = { sha256 = "9e05e33657efb4c6a9d23bd8300101536abd99c85cca82da0bffff8d8764d08a" } }, + { url = "https://files.pythonhosted.org/packages/bf/0a/130164a4881cec6ca8c00faf3b57926f28ed429cd6001a673f83c7c2a579/scipy-1.16.2-cp313-cp313t-macosx_14_0_arm64.whl", upload-time = 2025-09-11T17:42:40Z, size = 21381152, hashes = { sha256 = "7fe65b36036357003b3ef9d37547abeefaa353b237e989c21027b8ed62b12d4f" } }, + { url = "https://files.pythonhosted.org/packages/47/a6/503ffb0310ae77fba874e10cddfc4a1280bdcca1d13c3751b8c3c2996cf8/scipy-1.16.2-cp313-cp313t-macosx_14_0_x86_64.whl", upload-time = 2025-09-11T17:42:44Z, size = 23914410, hashes = { sha256 = "6406d2ac6d40b861cccf57f49592f9779071655e9f75cd4f977fa0bdd09cb2e4" } }, + { url = "https://files.pythonhosted.org/packages/fa/c7/1147774bcea50d00c02600aadaa919facbd8537997a62496270133536ed6/scipy-1.16.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-11T17:42:49Z, size = 33481880, hashes = { sha256 = "ff4dc42bd321991fbf611c23fc35912d690f731c9914bf3af8f417e64aca0f21" } }, + { url = "https://files.pythonhosted.org/packages/6a/74/99d5415e4c3e46b2586f30cdbecb95e101c7192628a484a40dd0d163811a/scipy-1.16.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-11T17:42:54Z, size = 35791425, hashes = { sha256 = "654324826654d4d9133e10675325708fb954bc84dae6e9ad0a52e75c6b1a01d7" } }, + { url = "https://files.pythonhosted.org/packages/1b/ee/a6559de7c1cc710e938c0355d9d4fbcd732dac4d0d131959d1f3b63eb29c/scipy-1.16.2-cp313-cp313t-musllinux_1_2_aarch64.whl", upload-time = 2025-09-11T17:43:00Z, size = 36178622, hashes = { sha256 = "63870a84cd15c44e65220eaed2dac0e8f8b26bbb991456a033c1d9abfe8a94f8" } }, + { url = "https://files.pythonhosted.org/packages/4e/7b/f127a5795d5ba8ece4e0dce7d4a9fb7cb9e4f4757137757d7a69ab7d4f1a/scipy-1.16.2-cp313-cp313t-musllinux_1_2_x86_64.whl", upload-time = 2025-09-11T17:43:06Z, size = 38783985, hashes = { sha256 = "fa01f0f6a3050fa6a9771a95d5faccc8e2f5a92b4a2e5440a0fa7264a2398472" } }, + { url = "https://files.pythonhosted.org/packages/3e/9f/bc81c1d1e033951eb5912cd3750cc005943afa3e65a725d2443a3b3c4347/scipy-1.16.2-cp313-cp313t-win_amd64.whl", upload-time = 2025-09-11T17:43:14Z, size = 38631367, hashes = { sha256 = "116296e89fba96f76353a8579820c2512f6e55835d3fad7780fece04367de351" } }, + { url = "https://files.pythonhosted.org/packages/d6/5e/2cc7555fd81d01814271412a1d59a289d25f8b63208a0a16c21069d55d3e/scipy-1.16.2-cp313-cp313t-win_arm64.whl", upload-time = 2025-09-11T17:43:19Z, size = 25787992, hashes = { sha256 = "98e22834650be81d42982360382b43b17f7ba95e0e6993e2a4f5b9ad9283a94d" } }, + { url = "https://files.pythonhosted.org/packages/8b/ac/ad8951250516db71619f0bd3b2eb2448db04b720a003dd98619b78b692c0/scipy-1.16.2-cp314-cp314-macosx_10_14_x86_64.whl", upload-time = 2025-09-11T17:43:35Z, size = 36595109, hashes = { sha256 = "567e77755019bb7461513c87f02bb73fb65b11f049aaaa8ca17cfaa5a5c45d77" } }, + { url = "https://files.pythonhosted.org/packages/ff/f6/5779049ed119c5b503b0f3dc6d6f3f68eefc3a9190d4ad4c276f854f051b/scipy-1.16.2-cp314-cp314-macosx_12_0_arm64.whl", upload-time = 2025-09-11T17:43:40Z, size = 28859110, hashes = { sha256 = "17d9bb346194e8967296621208fcdfd39b55498ef7d2f376884d5ac47cec1a70" } }, + { url = "https://files.pythonhosted.org/packages/82/09/9986e410ae38bf0a0c737ff8189ac81a93b8e42349aac009891c054403d7/scipy-1.16.2-cp314-cp314-macosx_14_0_arm64.whl", upload-time = 2025-09-11T17:43:44Z, size = 20850110, hashes = { sha256 = "0a17541827a9b78b777d33b623a6dcfe2ef4a25806204d08ead0768f4e529a88" } }, + { url = "https://files.pythonhosted.org/packages/0d/ad/485cdef2d9215e2a7df6d61b81d2ac073dfacf6ae24b9ae87274c4e936ae/scipy-1.16.2-cp314-cp314-macosx_14_0_x86_64.whl", upload-time = 2025-09-11T17:43:49Z, size = 23497014, hashes = { sha256 = "d7d4c6ba016ffc0f9568d012f5f1eb77ddd99412aea121e6fa8b4c3b7cbad91f" } }, + { url = "https://files.pythonhosted.org/packages/a7/74/f6a852e5d581122b8f0f831f1d1e32fb8987776ed3658e95c377d308ed86/scipy-1.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-11T17:43:54Z, size = 33401155, hashes = { sha256 = "9702c4c023227785c779cba2e1d6f7635dbb5b2e0936cdd3a4ecb98d78fd41eb" } }, + { url = "https://files.pythonhosted.org/packages/d9/f5/61d243bbc7c6e5e4e13dde9887e84a5cbe9e0f75fd09843044af1590844e/scipy-1.16.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-11T17:44:00Z, size = 35691174, hashes = { sha256 = "d1cdf0ac28948d225decdefcc45ad7dd91716c29ab56ef32f8e0d50657dffcc7" } }, + { url = "https://files.pythonhosted.org/packages/03/99/59933956331f8cc57e406cdb7a483906c74706b156998f322913e789c7e1/scipy-1.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-09-11T17:44:05Z, size = 36070752, hashes = { sha256 = "70327d6aa572a17c2941cdfb20673f82e536e91850a2e4cb0c5b858b690e1548" } }, + { url = "https://files.pythonhosted.org/packages/c6/7d/00f825cfb47ee19ef74ecf01244b43e95eae74e7e0ff796026ea7cd98456/scipy-1.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-09-11T17:44:11Z, size = 38701010, hashes = { sha256 = "5221c0b2a4b58aa7c4ed0387d360fd90ee9086d383bb34d9f2789fafddc8a936" } }, + { url = "https://files.pythonhosted.org/packages/e4/9f/b62587029980378304ba5a8563d376c96f40b1e133daacee76efdcae32de/scipy-1.16.2-cp314-cp314-win_amd64.whl", upload-time = 2025-09-11T17:45:09Z, size = 39360061, hashes = { sha256 = "f5a85d7b2b708025af08f060a496dd261055b617d776fc05a1a1cc69e09fe9ff" } }, + { url = "https://files.pythonhosted.org/packages/82/04/7a2f1609921352c7fbee0815811b5050582f67f19983096c4769867ca45f/scipy-1.16.2-cp314-cp314-win_arm64.whl", upload-time = 2025-09-11T17:45:14Z, size = 26126914, hashes = { sha256 = "2cc73a33305b4b24556957d5857d6253ce1e2dcd67fa0ff46d87d1670b3e1e1d" } }, + { url = "https://files.pythonhosted.org/packages/51/b9/60929ce350c16b221928725d2d1d7f86cf96b8bc07415547057d1196dc92/scipy-1.16.2-cp314-cp314t-macosx_10_14_x86_64.whl", upload-time = 2025-09-11T17:44:16Z, size = 37013193, hashes = { sha256 = "9ea2a3fed83065d77367775d689401a703d0f697420719ee10c0780bcab594d8" } }, + { url = "https://files.pythonhosted.org/packages/2a/41/ed80e67782d4bc5fc85a966bc356c601afddd175856ba7c7bb6d9490607e/scipy-1.16.2-cp314-cp314t-macosx_12_0_arm64.whl", upload-time = 2025-09-11T17:44:21Z, size = 29390172, hashes = { sha256 = "7280d926f11ca945c3ef92ba960fa924e1465f8d07ce3a9923080363390624c4" } }, + { url = "https://files.pythonhosted.org/packages/c4/a3/2f673ace4090452696ccded5f5f8efffb353b8f3628f823a110e0170b605/scipy-1.16.2-cp314-cp314t-macosx_14_0_arm64.whl", upload-time = 2025-09-11T17:44:25Z, size = 21381326, hashes = { sha256 = "8afae1756f6a1fe04636407ef7dbece33d826a5d462b74f3d0eb82deabefd831" } }, + { url = "https://files.pythonhosted.org/packages/42/bf/59df61c5d51395066c35836b78136accf506197617c8662e60ea209881e1/scipy-1.16.2-cp314-cp314t-macosx_14_0_x86_64.whl", upload-time = 2025-09-11T17:44:30Z, size = 23915036, hashes = { sha256 = "5c66511f29aa8d233388e7416a3f20d5cae7a2744d5cee2ecd38c081f4e861b3" } }, + { url = "https://files.pythonhosted.org/packages/91/c3/edc7b300dc16847ad3672f1a6f3f7c5d13522b21b84b81c265f4f2760d4a/scipy-1.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", upload-time = 2025-09-11T17:44:35Z, size = 33484341, hashes = { sha256 = "efe6305aeaa0e96b0ccca5ff647a43737d9a092064a3894e46c414db84bc54ac" } }, + { url = "https://files.pythonhosted.org/packages/26/c7/24d1524e72f06ff141e8d04b833c20db3021020563272ccb1b83860082a9/scipy-1.16.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", upload-time = 2025-09-11T17:44:41Z, size = 35790840, hashes = { sha256 = "7f3a337d9ae06a1e8d655ee9d8ecb835ea5ddcdcbd8d23012afa055ab014f374" } }, + { url = "https://files.pythonhosted.org/packages/aa/b7/5aaad984eeedd56858dc33d75efa59e8ce798d918e1033ef62d2708f2c3d/scipy-1.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", upload-time = 2025-09-11T17:44:47Z, size = 36174716, hashes = { sha256 = "bab3605795d269067d8ce78a910220262711b753de8913d3deeaedb5dded3bb6" } }, + { url = "https://files.pythonhosted.org/packages/fd/c2/e276a237acb09824822b0ada11b028ed4067fdc367a946730979feacb870/scipy-1.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", upload-time = 2025-09-11T17:44:53Z, size = 38790088, hashes = { sha256 = "b0348d8ddb55be2a844c518cd8cc8deeeb8aeba707cf834db5758fc89b476a2c" } }, + { url = "https://files.pythonhosted.org/packages/c6/b4/5c18a766e8353015439f3780f5fc473f36f9762edc1a2e45da3ff5a31b21/scipy-1.16.2-cp314-cp314t-win_amd64.whl", upload-time = 2025-09-11T17:44:58Z, size = 39457455, hashes = { sha256 = "26284797e38b8a75e14ea6631d29bda11e76ceaa6ddb6fdebbfe4c4d90faf2f9" } }, + { url = "https://files.pythonhosted.org/packages/97/30/2f9a5243008f76dfc5dee9a53dfb939d9b31e16ce4bd4f2e628bfc5d89d2/scipy-1.16.2-cp314-cp314t-win_arm64.whl", upload-time = 2025-09-11T17:45:03Z, size = 26448374, hashes = { sha256 = "d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779" } }, +] + +[[packages]] name = "sentence-transformers" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, - { name = "pillow" }, - { name = "scikit-learn" }, - { name = "scipy" }, - { name = "torch" }, - { name = "tqdm" }, - { name = "transformers" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/73/84/b30d1b29ff58cfdff423e36a50efd622c8e31d7039b1a0d5e72066620da1/sentence_transformers-4.1.0.tar.gz", hash = "sha256:f125ffd1c727533e0eca5d4567de72f84728de8f7482834de442fd90c2c3d50b", size = 272420 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/2d/1151b371f28caae565ad384fdc38198f1165571870217aedda230b9d7497/sentence_transformers-4.1.0-py3-none-any.whl", hash = "sha256:382a7f6be1244a100ce40495fb7523dbe8d71b3c10b299f81e6b735092b3b8ca", size = 345695 }, -] +version = "5.1.1" +sdist = { url = "https://files.pythonhosted.org/packages/21/47/7d61a19ba7e6b5f36f0ffff5bbf032a1c1913612caac611e12383069eda0/sentence_transformers-5.1.1.tar.gz", upload-time = 2025-09-22T11:28:27Z, size = 374434, hashes = { sha256 = "8af3f844b2ecf9a6c2dfeafc2c02938a87f61202b54329d70dfd7dfd7d17a84e" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/48/21/4670d03ab8587b0ab6f7d5fa02a95c3dd6b1f39d0e40e508870201f3d76c/sentence_transformers-5.1.1-py3-none-any.whl", upload-time = 2025-09-22T11:28:26Z, size = 486574, hashes = { sha256 = "5ed544629eafe89ca668a8910ebff96cf0a9c5254ec14b05c66c086226c892fd" } }] -[[package]] +[[packages]] name = "setuptools" -version = "80.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/0cc40fe41fd2adb80a2f388987f4f8db3c866c69e33e0b4c8b093fdf700e/setuptools-80.4.0.tar.gz", hash = "sha256:5a78f61820bc088c8e4add52932ae6b8cf423da2aff268c23f813cfbb13b4006", size = 1315008 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/93/dba5ed08c2e31ec7cdc2ce75705a484ef0be1a2fecac8a58272489349de8/setuptools-80.4.0-py3-none-any.whl", hash = "sha256:6cdc8cb9a7d590b237dbe4493614a9b75d0559b888047c1f67d49ba50fc3edb2", size = 1200812 }, -] +version = "80.9.0" +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", upload-time = 2025-05-27T00:56:51Z, size = 1319958, hashes = { sha256 = "f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", upload-time = 2025-05-27T00:56:49Z, size = 1201486, hashes = { sha256 = "062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922" } }] -[[package]] +[[packages]] 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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", upload-time = 2024-12-04T17:35:28Z, size = 34031, hashes = { sha256 = "ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", upload-time = 2024-12-04T17:35:26Z, size = 11050, hashes = { sha256 = "4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274" } }] -[[package]] +[[packages]] 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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", upload-time = 2024-02-25T23:20:04Z, size = 20372, hashes = { sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", upload-time = 2024-02-25T23:20:01Z, size = 10235, hashes = { sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2" } }] + +[[packages]] +name = "starlette" +version = "0.48.0" +sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", upload-time = 2025-09-13T08:41:05Z, size = 2652949, hashes = { sha256 = "7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", upload-time = 2025-09-13T08:41:03Z, size = 73736, hashes = { sha256 = "0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659" } }] -[[package]] +[[packages]] name = "sympy" version = "1.14.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mpmath" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", upload-time = 2025-04-27T18:05:01Z, size = 7793921, hashes = { sha256 = "d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", upload-time = 2025-04-27T18:04:59Z, size = 6299353, hashes = { sha256 = "e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5" } }] -[[package]] +[[packages]] name = "threadpoolctl" version = "3.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", upload-time = 2025-03-13T13:49:23Z, size = 21274, hashes = { sha256 = "8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", upload-time = 2025-03-13T13:49:21Z, size = 18638, hashes = { sha256 = "43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb" } }] -[[package]] +[[packages]] name = "tiktoken" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "regex" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ea/cf/756fedf6981e82897f2d570dd25fa597eb3f4459068ae0572d7e888cfd6f/tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d", size = 35991 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/11/09d936d37f49f4f494ffe660af44acd2d99eb2429d60a57c71318af214e0/tiktoken-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b0e8e05a26eda1249e824156d537015480af7ae222ccb798e5234ae0285dbdb", size = 1064919 }, - { url = "https://files.pythonhosted.org/packages/80/0e/f38ba35713edb8d4197ae602e80837d574244ced7fb1b6070b31c29816e0/tiktoken-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:27d457f096f87685195eea0165a1807fae87b97b2161fe8c9b1df5bd74ca6f63", size = 1007877 }, - { url = "https://files.pythonhosted.org/packages/fe/82/9197f77421e2a01373e27a79dd36efdd99e6b4115746ecc553318ecafbf0/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cf8ded49cddf825390e36dd1ad35cd49589e8161fdcb52aa25f0583e90a3e01", size = 1140095 }, - { url = "https://files.pythonhosted.org/packages/f2/bb/4513da71cac187383541facd0291c4572b03ec23c561de5811781bbd988f/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc156cb314119a8bb9748257a2eaebd5cc0753b6cb491d26694ed42fc7cb3139", size = 1195649 }, - { url = "https://files.pythonhosted.org/packages/fa/5c/74e4c137530dd8504e97e3a41729b1103a4ac29036cbfd3250b11fd29451/tiktoken-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cd69372e8c9dd761f0ab873112aba55a0e3e506332dd9f7522ca466e817b1b7a", size = 1258465 }, - { url = "https://files.pythonhosted.org/packages/de/a8/8f499c179ec900783ffe133e9aab10044481679bb9aad78436d239eee716/tiktoken-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:5ea0edb6f83dc56d794723286215918c1cde03712cbbafa0348b33448faf5b95", size = 894669 }, -] - -[[package]] +version = "0.12.0" +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", upload-time = 2025-10-06T20:22:45Z, size = 37806, hashes = { sha256 = "b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/b3/2cb7c17b6c4cf8ca983204255d3f1d95eda7213e247e6947a0ee2c747a2c/tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", upload-time = 2025-10-06T20:21:34Z, size = 1051991, hashes = { sha256 = "3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970" } }, + { url = "https://files.pythonhosted.org/packages/27/0f/df139f1df5f6167194ee5ab24634582ba9a1b62c6b996472b0277ec80f66/tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-10-06T20:21:35Z, size = 995798, hashes = { sha256 = "b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16" } }, + { url = "https://files.pythonhosted.org/packages/ef/5d/26a691f28ab220d5edc09b9b787399b130f24327ef824de15e5d85ef21aa/tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl", upload-time = 2025-10-06T20:21:36Z, size = 1129865, hashes = { sha256 = "cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030" } }, + { url = "https://files.pythonhosted.org/packages/b2/94/443fab3d4e5ebecac895712abd3849b8da93b7b7dec61c7db5c9c7ebe40c/tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl", upload-time = 2025-10-06T20:21:37Z, size = 1152856, hashes = { sha256 = "6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134" } }, + { url = "https://files.pythonhosted.org/packages/54/35/388f941251b2521c70dd4c5958e598ea6d2c88e28445d2fb8189eecc1dfc/tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-10-06T20:21:39Z, size = 1195308, hashes = { sha256 = "6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a" } }, + { url = "https://files.pythonhosted.org/packages/f8/00/c6681c7f833dd410576183715a530437a9873fa910265817081f65f9105f/tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-10-06T20:21:41Z, size = 1255697, hashes = { sha256 = "82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892" } }, + { url = "https://files.pythonhosted.org/packages/5f/d2/82e795a6a9bafa034bf26a58e68fe9a89eeaaa610d51dbeb22106ba04f0a/tiktoken-0.12.0-cp310-cp310-win_amd64.whl", upload-time = 2025-10-06T20:21:43Z, size = 879375, hashes = { sha256 = "6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1" } }, + { url = "https://files.pythonhosted.org/packages/de/46/21ea696b21f1d6d1efec8639c204bdf20fde8bafb351e1355c72c5d7de52/tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", upload-time = 2025-10-06T20:21:44Z, size = 1051565, hashes = { sha256 = "6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb" } }, + { url = "https://files.pythonhosted.org/packages/c9/d9/35c5d2d9e22bb2a5f74ba48266fb56c63d76ae6f66e02feb628671c0283e/tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-10-06T20:21:45Z, size = 995284, hashes = { sha256 = "c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa" } }, + { url = "https://files.pythonhosted.org/packages/01/84/961106c37b8e49b9fdcf33fe007bb3a8fdcc380c528b20cc7fbba80578b8/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", upload-time = 2025-10-06T20:21:47Z, size = 1129201, hashes = { sha256 = "f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc" } }, + { url = "https://files.pythonhosted.org/packages/6a/d0/3d9275198e067f8b65076a68894bb52fd253875f3644f0a321a720277b8a/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", upload-time = 2025-10-06T20:21:48Z, size = 1152444, hashes = { sha256 = "47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded" } }, + { url = "https://files.pythonhosted.org/packages/78/db/a58e09687c1698a7c592e1038e01c206569b86a0377828d51635561f8ebf/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-10-06T20:21:49Z, size = 1195080, hashes = { sha256 = "508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd" } }, + { url = "https://files.pythonhosted.org/packages/9e/1b/a9e4d2bf91d515c0f74afc526fd773a812232dd6cda33ebea7f531202325/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-10-06T20:21:50Z, size = 1255240, hashes = { sha256 = "a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967" } }, + { url = "https://files.pythonhosted.org/packages/9d/15/963819345f1b1fb0809070a79e9dd96938d4ca41297367d471733e79c76c/tiktoken-0.12.0-cp311-cp311-win_amd64.whl", upload-time = 2025-10-06T20:21:51Z, size = 879422, hashes = { sha256 = "3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def" } }, + { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2025-10-06T20:21:52Z, size = 1050728, hashes = { sha256 = "b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8" } }, + { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-10-06T20:21:53Z, size = 994049, hashes = { sha256 = "2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b" } }, + { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", upload-time = 2025-10-06T20:21:54Z, size = 1129008, hashes = { sha256 = "65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37" } }, + { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", upload-time = 2025-10-06T20:21:56Z, size = 1152665, hashes = { sha256 = "edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad" } }, + { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-10-06T20:21:57Z, size = 1194230, hashes = { sha256 = "35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5" } }, + { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-10-06T20:21:58Z, size = 1254688, hashes = { sha256 = "83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3" } }, + { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", upload-time = 2025-10-06T20:21:59Z, size = 878694, hashes = { sha256 = "ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd" } }, + { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-10-06T20:22:00Z, size = 1050802, hashes = { sha256 = "775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3" } }, + { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-10-06T20:22:02Z, size = 993995, hashes = { sha256 = "a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160" } }, + { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", upload-time = 2025-10-06T20:22:03Z, size = 1128948, hashes = { sha256 = "01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa" } }, + { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", upload-time = 2025-10-06T20:22:05Z, size = 1151986, hashes = { sha256 = "4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be" } }, + { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-10-06T20:22:06Z, size = 1194222, hashes = { sha256 = "981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a" } }, + { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-10-06T20:22:07Z, size = 1255097, hashes = { sha256 = "9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3" } }, + { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", upload-time = 2025-10-06T20:22:08Z, size = 879117, hashes = { sha256 = "b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697" } }, + { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", upload-time = 2025-10-06T20:22:10Z, size = 1050309, hashes = { sha256 = "d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16" } }, + { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-10-06T20:22:12Z, size = 993712, hashes = { sha256 = "b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a" } }, + { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", upload-time = 2025-10-06T20:22:13Z, size = 1128725, hashes = { sha256 = "fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27" } }, + { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", upload-time = 2025-10-06T20:22:14Z, size = 1151875, hashes = { sha256 = "06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb" } }, + { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", upload-time = 2025-10-06T20:22:15Z, size = 1194451, hashes = { sha256 = "04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e" } }, + { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", upload-time = 2025-10-06T20:22:16Z, size = 1253794, hashes = { sha256 = "0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25" } }, + { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", upload-time = 2025-10-06T20:22:18Z, size = 878777, hashes = { sha256 = "dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f" } }, + { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", upload-time = 2025-10-06T20:22:19Z, size = 1050188, hashes = { sha256 = "a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646" } }, + { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-10-06T20:22:20Z, size = 993978, hashes = { sha256 = "da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88" } }, + { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", upload-time = 2025-10-06T20:22:22Z, size = 1129271, hashes = { sha256 = "285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff" } }, + { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", upload-time = 2025-10-06T20:22:23Z, size = 1151216, hashes = { sha256 = "d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830" } }, + { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2025-10-06T20:22:24Z, size = 1194860, hashes = { sha256 = "604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b" } }, + { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2025-10-06T20:22:25Z, size = 1254567, hashes = { sha256 = "8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b" } }, + { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", upload-time = 2025-10-06T20:22:26Z, size = 921067, hashes = { sha256 = "399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3" } }, + { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", upload-time = 2025-10-06T20:22:27Z, size = 1050473, hashes = { sha256 = "c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365" } }, + { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-10-06T20:22:28Z, size = 993855, hashes = { sha256 = "cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e" } }, + { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", upload-time = 2025-10-06T20:22:29Z, size = 1129022, hashes = { sha256 = "dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63" } }, + { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", upload-time = 2025-10-06T20:22:30Z, size = 1150736, hashes = { sha256 = "584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0" } }, + { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", upload-time = 2025-10-06T20:22:32Z, size = 1194908, hashes = { sha256 = "54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a" } }, + { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", upload-time = 2025-10-06T20:22:33Z, size = 1253706, hashes = { sha256 = "5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0" } }, + { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", upload-time = 2025-10-06T20:22:34Z, size = 920667, hashes = { sha256 = "f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71" } }, + { url = "https://files.pythonhosted.org/packages/c7/d1/7507bfb9c2ceef52ae3ae813013215c185648e21127538aae66dedd3af9c/tiktoken-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", upload-time = 2025-10-06T20:22:35Z, size = 1053407, hashes = { sha256 = "d51d75a5bffbf26f86554d28e78bfb921eae998edc2675650fd04c7e1f0cdc1e" } }, + { url = "https://files.pythonhosted.org/packages/ee/4a/8ea1da602ac39dee4356b4cd6040a2325507482c36043044b6f581597b4f/tiktoken-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2025-10-06T20:22:37Z, size = 997150, hashes = { sha256 = "09eb4eae62ae7e4c62364d9ec3a57c62eea707ac9a2b2c5d6bd05de6724ea179" } }, + { url = "https://files.pythonhosted.org/packages/2c/1a/62d1d36b167eccd441aff2f0091551ca834295541b949d161021aa658167/tiktoken-0.12.0-cp39-cp39-manylinux_2_28_aarch64.whl", upload-time = 2025-10-06T20:22:39Z, size = 1131575, hashes = { sha256 = "df37684ace87d10895acb44b7f447d4700349b12197a526da0d4a4149fde074c" } }, + { url = "https://files.pythonhosted.org/packages/f7/16/544207d63c8c50edd2321228f21d236e4e49d235128bb7e3e0f69eed0807/tiktoken-0.12.0-cp39-cp39-manylinux_2_28_x86_64.whl", upload-time = 2025-10-06T20:22:40Z, size = 1154920, hashes = { sha256 = "4c9614597ac94bb294544345ad8cf30dac2129c05e2db8dc53e082f355857af7" } }, + { url = "https://files.pythonhosted.org/packages/99/4c/0a3504157c81364fc0c64cada54efef0567961357e786706ea63bc8946e1/tiktoken-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", upload-time = 2025-10-06T20:22:41Z, size = 1196766, hashes = { sha256 = "20cf97135c9a50de0b157879c3c4accbb29116bcf001283d26e073ff3b345946" } }, + { url = "https://files.pythonhosted.org/packages/d4/46/8e6a258ae65447c75770fe5ea8968acab369e8c9f537f727c91f83772325/tiktoken-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", upload-time = 2025-10-06T20:22:42Z, size = 1258278, hashes = { sha256 = "15d875454bbaa3728be39880ddd11a5a2a9e548c29418b41e8fd8a767172b5ec" } }, + { url = "https://files.pythonhosted.org/packages/35/43/3b95de4f5e76f3cafc70dac9b1b9cfe759ff3bfd494ac91a280e93772e90/tiktoken-0.12.0-cp39-cp39-win_amd64.whl", upload-time = 2025-10-06T20:22:44Z, size = 881888, hashes = { sha256 = "2cff3688ba3c639ebe816f8d58ffbbb0aa7433e23e08ab1cade5d175fc973fb3" } }, +] + +[[packages]] name = "tokenizers" -version = "0.21.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/92/76/5ac0c97f1117b91b7eb7323dcd61af80d72f790b4df71249a7850c195f30/tokenizers-0.21.1.tar.gz", hash = "sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab", size = 343256 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/1f/328aee25f9115bf04262e8b4e5a2050b7b7cf44b59c74e982db7270c7f30/tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41", size = 2780767 }, - { url = "https://files.pythonhosted.org/packages/ae/1a/4526797f3719b0287853f12c5ad563a9be09d446c44ac784cdd7c50f76ab/tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3", size = 2650555 }, - { url = "https://files.pythonhosted.org/packages/4d/7a/a209b29f971a9fdc1da86f917fe4524564924db50d13f0724feed37b2a4d/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f", size = 2937541 }, - { url = "https://files.pythonhosted.org/packages/3c/1e/b788b50ffc6191e0b1fc2b0d49df8cff16fe415302e5ceb89f619d12c5bc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf", size = 2819058 }, - { url = "https://files.pythonhosted.org/packages/36/aa/3626dfa09a0ecc5b57a8c58eeaeb7dd7ca9a37ad9dd681edab5acd55764c/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8", size = 3133278 }, - { url = "https://files.pythonhosted.org/packages/a4/4d/8fbc203838b3d26269f944a89459d94c858f5b3f9a9b6ee9728cdcf69161/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0", size = 3144253 }, - { url = "https://files.pythonhosted.org/packages/d8/1b/2bd062adeb7c7511b847b32e356024980c0ffcf35f28947792c2d8ad2288/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c", size = 3398225 }, - { url = "https://files.pythonhosted.org/packages/8a/63/38be071b0c8e06840bc6046991636bcb30c27f6bb1e670f4f4bc87cf49cc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a", size = 3038874 }, - { url = "https://files.pythonhosted.org/packages/ec/83/afa94193c09246417c23a3c75a8a0a96bf44ab5630a3015538d0c316dd4b/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf", size = 9014448 }, - { url = "https://files.pythonhosted.org/packages/ae/b3/0e1a37d4f84c0f014d43701c11eb8072704f6efe8d8fc2dcdb79c47d76de/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6", size = 8937877 }, - { url = "https://files.pythonhosted.org/packages/ac/33/ff08f50e6d615eb180a4a328c65907feb6ded0b8f990ec923969759dc379/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d", size = 9186645 }, - { url = "https://files.pythonhosted.org/packages/5f/aa/8ae85f69a9f6012c6f8011c6f4aa1c96154c816e9eea2e1b758601157833/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f", size = 9384380 }, - { url = "https://files.pythonhosted.org/packages/e8/5b/a5d98c89f747455e8b7a9504910c865d5e51da55e825a7ae641fb5ff0a58/tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3", size = 2239506 }, - { url = "https://files.pythonhosted.org/packages/e6/b6/072a8e053ae600dcc2ac0da81a23548e3b523301a442a6ca900e92ac35be/tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382", size = 2435481 }, -] - -[[package]] +version = "0.22.1" +sdist = { url = "https://files.pythonhosted.org/packages/1c/46/fb6854cec3278fbfa4a75b50232c77622bc517ac886156e6afbfa4d8fc6e/tokenizers-0.22.1.tar.gz", upload-time = 2025-09-19T09:49:23Z, size = 363123, hashes = { sha256 = "61de6522785310a309b3407bac22d99c4db5dba349935e99e4d15ea2226af2d9" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/33/f4b2d94ada7ab297328fc671fed209368ddb82f965ec2224eb1892674c3a/tokenizers-0.22.1-cp39-abi3-macosx_10_12_x86_64.whl", upload-time = 2025-09-19T09:49:11Z, size = 3069318, hashes = { sha256 = "59fdb013df17455e5f950b4b834a7b3ee2e0271e6378ccb33aa74d178b513c73" } }, + { url = "https://files.pythonhosted.org/packages/1c/58/2aa8c874d02b974990e89ff95826a4852a8b2a273c7d1b4411cdd45a4565/tokenizers-0.22.1-cp39-abi3-macosx_11_0_arm64.whl", upload-time = 2025-09-19T09:49:09Z, size = 2926478, hashes = { sha256 = "8d4e484f7b0827021ac5f9f71d4794aaef62b979ab7608593da22b1d2e3c4edc" } }, + { url = "https://files.pythonhosted.org/packages/1e/3b/55e64befa1e7bfea963cf4b787b2cea1011362c4193f5477047532ce127e/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-09-19T09:48:56Z, size = 3256994, hashes = { sha256 = "19d2962dd28bc67c1f205ab180578a78eef89ac60ca7ef7cbe9635a46a56422a" } }, + { url = "https://files.pythonhosted.org/packages/71/0b/fbfecf42f67d9b7b80fde4aabb2b3110a97fac6585c9470b5bff103a80cb/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-09-19T09:48:59Z, size = 3153141, hashes = { sha256 = "38201f15cdb1f8a6843e6563e6e79f4abd053394992b9bbdf5213ea3469b4ae7" } }, + { url = "https://files.pythonhosted.org/packages/17/a9/b38f4e74e0817af8f8ef925507c63c6ae8171e3c4cb2d5d4624bf58fca69/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-09-19T09:49:05Z, size = 3508049, hashes = { sha256 = "d1cbe5454c9a15df1b3443c726063d930c16f047a3cc724b9e6e1a91140e5a21" } }, + { url = "https://files.pythonhosted.org/packages/d2/48/dd2b3dac46bb9134a88e35d72e1aa4869579eacc1a27238f1577270773ff/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-09-19T09:49:01Z, size = 3710730, hashes = { sha256 = "e7d094ae6312d69cc2a872b54b91b309f4f6fbce871ef28eb27b52a98e4d0214" } }, + { url = "https://files.pythonhosted.org/packages/93/0e/ccabc8d16ae4ba84a55d41345207c1e2ea88784651a5a487547d80851398/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-09-19T09:49:03Z, size = 3412560, hashes = { sha256 = "afd7594a56656ace95cdd6df4cca2e4059d294c5cfb1679c57824b605556cb2f" } }, + { url = "https://files.pythonhosted.org/packages/d0/c6/dc3a0db5a6766416c32c034286d7c2d406da1f498e4de04ab1b8959edd00/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-09-19T09:49:07Z, size = 3250221, hashes = { sha256 = "e2ef6063d7a84994129732b47e7915e8710f27f99f3a3260b8a38fc7ccd083f4" } }, + { url = "https://files.pythonhosted.org/packages/d7/a6/2c8486eef79671601ff57b093889a345dd3d576713ef047776015dc66de7/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_aarch64.whl", upload-time = 2025-09-19T09:49:14Z, size = 9345569, hashes = { sha256 = "ba0a64f450b9ef412c98f6bcd2a50c6df6e2443b560024a09fa6a03189726879" } }, + { url = "https://files.pythonhosted.org/packages/6b/16/32ce667f14c35537f5f605fe9bea3e415ea1b0a646389d2295ec348d5657/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_armv7l.whl", upload-time = 2025-09-19T09:49:16Z, size = 9271599, hashes = { sha256 = "331d6d149fa9c7d632cde4490fb8bbb12337fa3a0232e77892be656464f4b446" } }, + { url = "https://files.pythonhosted.org/packages/51/7c/a5f7898a3f6baa3fc2685c705e04c98c1094c523051c805cdd9306b8f87e/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_i686.whl", upload-time = 2025-09-19T09:49:19Z, size = 9533862, hashes = { sha256 = "607989f2ea68a46cb1dfbaf3e3aabdf3f21d8748312dbeb6263d1b3b66c5010a" } }, + { url = "https://files.pythonhosted.org/packages/36/65/7e75caea90bc73c1dd8d40438adf1a7bc26af3b8d0a6705ea190462506e1/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_x86_64.whl", upload-time = 2025-09-19T09:49:21Z, size = 9681250, hashes = { sha256 = "a0f307d490295717726598ef6fa4f24af9d484809223bbc253b201c740a06390" } }, + { url = "https://files.pythonhosted.org/packages/30/2c/959dddef581b46e6209da82df3b78471e96260e2bc463f89d23b1bf0e52a/tokenizers-0.22.1-cp39-abi3-win32.whl", upload-time = 2025-09-19T09:49:27Z, size = 2472003, hashes = { sha256 = "b5120eed1442765cd90b903bb6cfef781fd8fe64e34ccaecbae4c619b7b12a82" } }, + { url = "https://files.pythonhosted.org/packages/b3/46/e33a8c93907b631a99377ef4c5f817ab453d0b34f93529421f42ff559671/tokenizers-0.22.1-cp39-abi3-win_amd64.whl", upload-time = 2025-09-19T09:49:24Z, size = 2674684, hashes = { sha256 = "65fd6e3fb11ca1e78a6a93602490f134d1fdeb13bcef99389d5102ea318ed138" } }, +] + +[[packages]] +name = "toml-sort" +version = "0.24.3" +sdist = { url = "https://files.pythonhosted.org/packages/15/65/d62fb175769355a65c2c5e53a155cf28f2162af4e4067f11d8af89c0a4a3/toml_sort-0.24.3.tar.gz", upload-time = 2025-09-10T02:43:54Z, size = 17773, hashes = { sha256 = "74c3cab5c1ecaf76c8239addc15342de548fb39598694f1298550d0fe2673a81" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/0e/1c/f5a9a4f918ff0e1578fdc3f607a7ec704c5da1b13ddc290fd0aefa97ff2e/toml_sort-0.24.3-py3-none-any.whl", upload-time = 2025-09-10T02:43:53Z, size = 16538, hashes = { sha256 = "d70642f54827b4c17a6cde788dcb45d598c1eb9833544a2fcec5eb7b0b744ed4" } }] + +[[packages]] +name = "tomlkit" +version = "0.13.3" +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", upload-time = 2025-06-05T07:13:44Z, size = 185207, hashes = { sha256 = "430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", upload-time = 2025-06-05T07:13:43Z, size = 38901, hashes = { sha256 = "c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0" } }] + +[[packages]] name = "torch" -version = "2.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "jinja2" }, - { name = "networkx" }, - { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools" }, - { name = "sympy" }, - { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "typing-extensions" }, -] +version = "2.9.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/14/24/720ea9a66c29151b315ea6ba6f404650834af57a26b2a04af23ec246b2d5/torch-2.7.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:868ccdc11798535b5727509480cd1d86d74220cfdc42842c4617338c1109a205", size = 99015553 }, - { url = "https://files.pythonhosted.org/packages/4b/27/285a8cf12bd7cd71f9f211a968516b07dcffed3ef0be585c6e823675ab91/torch-2.7.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9b52347118116cf3dff2ab5a3c3dd97c719eb924ac658ca2a7335652076df708", size = 865046389 }, - { url = "https://files.pythonhosted.org/packages/74/c8/2ab2b6eadc45554af8768ae99668c5a8a8552e2012c7238ded7e9e4395e1/torch-2.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:434cf3b378340efc87c758f250e884f34460624c0523fe5c9b518d205c91dd1b", size = 212490304 }, - { url = "https://files.pythonhosted.org/packages/28/fd/74ba6fde80e2b9eef4237fe668ffae302c76f0e4221759949a632ca13afa/torch-2.7.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:edad98dddd82220465b106506bb91ee5ce32bd075cddbcf2b443dfaa2cbd83bf", size = 68856166 }, - { url = "https://files.pythonhosted.org/packages/cb/b4/8df3f9fe6bdf59e56a0e538592c308d18638eb5f5dc4b08d02abb173c9f0/torch-2.7.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2a885fc25afefb6e6eb18a7d1e8bfa01cc153e92271d980a49243b250d5ab6d9", size = 99091348 }, - { url = "https://files.pythonhosted.org/packages/9d/f5/0bd30e9da04c3036614aa1b935a9f7e505a9e4f1f731b15e165faf8a4c74/torch-2.7.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:176300ff5bc11a5f5b0784e40bde9e10a35c4ae9609beed96b4aeb46a27f5fae", size = 865104023 }, - { url = "https://files.pythonhosted.org/packages/d1/b7/2235d0c3012c596df1c8d39a3f4afc1ee1b6e318d469eda4c8bb68566448/torch-2.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d0ca446a93f474985d81dc866fcc8dccefb9460a29a456f79d99c29a78a66993", size = 212750916 }, - { url = "https://files.pythonhosted.org/packages/90/48/7e6477cf40d48cc0a61fa0d41ee9582b9a316b12772fcac17bc1a40178e7/torch-2.7.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:27f5007bdf45f7bb7af7f11d1828d5c2487e030690afb3d89a651fd7036a390e", size = 68575074 }, -] - -[[package]] + { url = "https://files.pythonhosted.org/packages/bb/86/245c240d2138c17ed572c943c289056c2721abab70810d772c6bf5495b28/torch-2.9.0-cp310-cp310-manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:45:59Z, size = 104213554, hashes = { sha256 = "030bbfe367379ae6a4ae4042b6c44da25383343b8b3c68abaa9c7231efbaf2dd" } }, + { url = "https://files.pythonhosted.org/packages/58/1d/fd1e88ae0948825efcab7dd66d12bec23f05d4d38ed81573c8d453c14c06/torch-2.9.0-cp310-cp310-manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T15:47:12Z, size = 899795167, hashes = { sha256 = "51cb63902182a78e90886e8068befd8ea102af4b00e420263591a3d70c7d3c6c" } }, + { url = "https://files.pythonhosted.org/packages/63/5a/496197b45c14982bef4e079b24c61dc108e3ab0d0cc9718dba9f54f45a46/torch-2.9.0-cp310-cp310-win_amd64.whl", upload-time = 2025-10-15T15:46:16Z, size = 109310314, hashes = { sha256 = "3f6aad4d2f0ee2248bac25339d74858ff846c3969b27d14ac235821f055af83d" } }, + { url = "https://files.pythonhosted.org/packages/58/b0/2b4e647b0fc706e88eb6c253d05511865578f5f67b55fad639bf3272a4a1/torch-2.9.0-cp310-none-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:46:04Z, size = 74452019, hashes = { sha256 = "413e1654c9203733138858780e184d9fc59442f0b3b209e16f39354eb893db9b" } }, + { url = "https://files.pythonhosted.org/packages/58/fe/334225e6330e672b36aef23d77451fa906ea12881570c08638a91331a212/torch-2.9.0-cp311-cp311-manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:46:08Z, size = 104230578, hashes = { sha256 = "c596708b5105d0b199215acf0c9be7c1db5f1680d88eddadf4b75a299259a677" } }, + { url = "https://files.pythonhosted.org/packages/05/cc/49566caaa218872ec9a2912456f470ff92649894a4bc2e5274aa9ef87c4a/torch-2.9.0-cp311-cp311-manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T15:48:03Z, size = 899815990, hashes = { sha256 = "51de31219c97c51cf4bf2be94d622e3deb5dcc526c6dc00e97c17eaec0fc1d67" } }, + { url = "https://files.pythonhosted.org/packages/74/25/e9ab21d5925b642d008f139d4a3c9664fc9ee1faafca22913c080cc4c0a5/torch-2.9.0-cp311-cp311-win_amd64.whl", upload-time = 2025-10-15T15:46:12Z, size = 109313698, hashes = { sha256 = "dd515c70059afd95f48b8192733764c08ca37a1d19803af6401b5ecad7c8676e" } }, + { url = "https://files.pythonhosted.org/packages/b3/b7/205ef3e94de636feffd64b28bb59a0dfac0771221201b9871acf9236f5ca/torch-2.9.0-cp311-none-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:46:29Z, size = 74463678, hashes = { sha256 = "614a185e4986326d526a91210c8fc1397e76e8cfafa78baf6296a790e53a9eec" } }, + { url = "https://files.pythonhosted.org/packages/d1/d3/3985739f3b8e88675127bf70f82b3a48ae083e39cda56305dbd90398fec0/torch-2.9.0-cp312-cp312-manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:46:20Z, size = 104107898, hashes = { sha256 = "e5f7af1dc4c0a7c4a260c2534f41ddaf209714f7c89145e644c44712fbd6b642" } }, + { url = "https://files.pythonhosted.org/packages/a5/4b/f4bb2e6c25d0272f798cd6d7a04ed315da76cec68c602d87040c7847287f/torch-2.9.0-cp312-cp312-manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T15:50:04Z, size = 899738273, hashes = { sha256 = "01cff95ecd9a212ea2f141db28acccdceb6a4c54f64e6c51091146f5e2a772c6" } }, + { url = "https://files.pythonhosted.org/packages/66/11/c1c5ba6691cda6279087c35bd626536e4fd29521fe740abf5008377a9a02/torch-2.9.0-cp312-cp312-win_amd64.whl", upload-time = 2025-10-15T15:46:26Z, size = 109280887, hashes = { sha256 = "4582b162f541651f0cb184d3e291c05c2f556c7117c64a9873e2ee158d40062b" } }, + { url = "https://files.pythonhosted.org/packages/dd/5f/b85bd8c05312d71de9402bf5868d217c38827cfd09d8f8514e5be128a52b/torch-2.9.0-cp312-none-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:46:39Z, size = 74478983, hashes = { sha256 = "33f58e9a102a91259af289d50525c30323b5c9ae1d31322b6447c0814da68695" } }, + { url = "https://files.pythonhosted.org/packages/c2/1c/90eb13833cdf4969ea9707586d7b57095c3b6e2b223a7256bf111689bcb8/torch-2.9.0-cp313-cp313-manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:46:35Z, size = 104111330, hashes = { sha256 = "c30a17fc83eeab346913e237c64b15b5ba6407fff812f6c541e322e19bc9ea0e" } }, + { url = "https://files.pythonhosted.org/packages/0e/21/2254c54b8d523592c25ef4434769aa23e29b1e6bf5f4c0ad9e27bf442927/torch-2.9.0-cp313-cp313-manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T15:48:57Z, size = 899750243, hashes = { sha256 = "8f25033b8667b57857dfd01458fbf2a9e6a6df1f8def23aef0dc46292f6aa642" } }, + { url = "https://files.pythonhosted.org/packages/b7/a5/5cb94fa4fd1e78223455c23c200f30f6dc10c6d4a2bcc8f6e7f2a2588370/torch-2.9.0-cp313-cp313-win_amd64.whl", upload-time = 2025-10-15T15:46:45Z, size = 109284513, hashes = { sha256 = "d037f1b4ffd25013be4a7bf3651a0a910c68554956c7b2c92ebe87c76475dece" } }, + { url = "https://files.pythonhosted.org/packages/66/e8/fc414d8656250ee46120b44836ffbb3266343db424b3e18ca79ebbf69d4f/torch-2.9.0-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:46:48Z, size = 74830362, hashes = { sha256 = "e4e5b5cba837a2a8d1a497ba9a58dae46fa392593eaa13b871c42f71847503a5" } }, + { url = "https://files.pythonhosted.org/packages/ed/5f/9474c98fc5ae0cd04b9466035428cd360e6611a86b8352a0fc2fa504acdc/torch-2.9.0-cp313-cp313t-manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:47:29Z, size = 104144940, hashes = { sha256 = "64693568f5dc4dbd5f880a478b1cea0201cc6b510d91d1bc54fea86ac5d1a637" } }, + { url = "https://files.pythonhosted.org/packages/2d/5a/8e0c1cf57830172c109d4bd6be2708cabeaf550983eee7029291322447a0/torch-2.9.0-cp313-cp313t-manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T15:48:29Z, size = 899744054, hashes = { sha256 = "f8ed31ddd7d10bfb3fbe0b9fe01b1243577f13d75e6f4a0839a283915ce3791e" } }, + { url = "https://files.pythonhosted.org/packages/6d/28/82c28b30fcb4b7c9cdd995763d18bbb830d6521356712faebbad92ffa61d/torch-2.9.0-cp313-cp313t-win_amd64.whl", upload-time = 2025-10-15T15:47:33Z, size = 109517546, hashes = { sha256 = "eff527d4e4846e6f70d2afd8058b73825761203d66576a7e04ea2ecfebcb4ab8" } }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a91f96ec74347fa5fd24453fa514bc61c61ecc79196fa760b012a1873d96/torch-2.9.0-cp313-none-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:47:38Z, size = 74480732, hashes = { sha256 = "f8877779cf56d1ce431a7636703bdb13307f5960bb1af49716d8b179225e0e6a" } }, + { url = "https://files.pythonhosted.org/packages/5c/73/9f70af34b334a7e0ef496ceec96b7ec767bd778ea35385ce6f77557534d1/torch-2.9.0-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:47:41Z, size = 74433037, hashes = { sha256 = "7e614fae699838038d888729f82b687c03413c5989ce2a9481f9a7e7a396e0bb" } }, + { url = "https://files.pythonhosted.org/packages/b7/84/37cf88625901934c97109e583ecc21777d21c6f54cda97a7e5bbad1ee2f2/torch-2.9.0-cp314-cp314-manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:47:46Z, size = 104116482, hashes = { sha256 = "dfb5b8cd310ba3436c7e14e8b7833ef658cf3045e50d2bdaed23c8fc517065eb" } }, + { url = "https://files.pythonhosted.org/packages/56/8e/ca8b17866943a8d4f4664d402ea84210aa274588b4c5d89918f5caa24eec/torch-2.9.0-cp314-cp314-manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T15:50:40Z, size = 899746916, hashes = { sha256 = "b3d29524993a478e46f5d598b249cd824b7ed98d7fba538bd9c4cde6c803948f" } }, + { url = "https://files.pythonhosted.org/packages/43/65/3b17c0fbbdab6501c5b320a52a648628d0d44e7379f64e27d9eef701b6bf/torch-2.9.0-cp314-cp314-win_amd64.whl", upload-time = 2025-10-15T15:49:20Z, size = 109275151, hashes = { sha256 = "71c7578984f5ec0eb645eb4816ac8435fcf3e3e2ae1901bcd2f519a9cafb5125" } }, + { url = "https://files.pythonhosted.org/packages/83/36/74f8c051f785500396e42f93542422422dfd874a174f21f8d955d36e5d64/torch-2.9.0-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-10-15T15:49:16Z, size = 74823353, hashes = { sha256 = "71d9309aee457bbe0b164bce2111cd911c4ed4e847e65d5077dbbcd3aba6befc" } }, + { url = "https://files.pythonhosted.org/packages/62/51/dc3b4e2f9ba98ae27238f0153ca098bf9340b2dafcc67fde645d496dfc2a/torch-2.9.0-cp314-cp314t-manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T15:50:19Z, size = 104140340, hashes = { sha256 = "c08fb654d783899e204a32cca758a7ce8a45b2d78eeb89517cc937088316f78e" } }, + { url = "https://files.pythonhosted.org/packages/c0/8d/b00657f8141ac16af7bb6cda2e67de18499a3263b78d516b9a93fcbc98e3/torch-2.9.0-cp314-cp314t-manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T15:49:36Z, size = 899731750, hashes = { sha256 = "ec8feb0099b2daa5728fbc7abb0b05730fd97e0f359ff8bda09865aaa7bd7d4b" } }, + { url = "https://files.pythonhosted.org/packages/fc/29/bd361e0cbb2c79ce6450f42643aaf6919956f89923a50571b0ebfe92d142/torch-2.9.0-cp314-cp314t-win_amd64.whl", upload-time = 2025-10-15T15:50:24Z, size = 109503850, hashes = { sha256 = "695ba920f234ad4170c9c50e28d56c848432f8f530e6bc7f88fcb15ddf338e75" } }, +] + +[[packages]] name = "tqdm" version = "4.67.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", upload-time = 2024-11-24T20:12:22Z, size = 169737, hashes = { sha256 = "f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", upload-time = 2024-11-24T20:12:19Z, size = 78540, hashes = { sha256 = "26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2" } }] -[[package]] +[[packages]] name = "transformers" -version = "4.51.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "huggingface-hub" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "regex" }, - { name = "requests" }, - { name = "safetensors" }, - { name = "tokenizers" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/11/7414d5bc07690002ce4d7553602107bf969af85144bbd02830f9fb471236/transformers-4.51.3.tar.gz", hash = "sha256:e292fcab3990c6defe6328f0f7d2004283ca81a7a07b2de9a46d67fd81ea1409", size = 8941266 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/b6/5257d04ae327b44db31f15cce39e6020cc986333c715660b1315a9724d82/transformers-4.51.3-py3-none-any.whl", hash = "sha256:fd3279633ceb2b777013234bbf0b4f5c2d23c4626b05497691f00cfda55e8a83", size = 10383940 }, -] +version = "4.57.1" +sdist = { url = "https://files.pythonhosted.org/packages/d6/68/a39307bcc4116a30b2106f2e689130a48de8bd8a1e635b5e1030e46fcd9e/transformers-4.57.1.tar.gz", upload-time = 2025-10-14T15:39:26Z, size = 10142511, hashes = { sha256 = "f06c837959196c75039809636cd964b959f6604b75b8eeec6fdfc0440b89cc55" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/71/d3/c16c3b3cf7655a67db1144da94b021c200ac1303f82428f2beef6c2e72bb/transformers-4.57.1-py3-none-any.whl", upload-time = 2025-10-14T15:39:23Z, size = 11990925, hashes = { sha256 = "b10d05da8fa67dc41644dbbf9bc45a44cb86ae33da6f9295f5fbf5b7890bd267" } }] -[[package]] +[[packages]] name = "triton" -version = "3.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "setuptools" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/74/4bf2702b65e93accaa20397b74da46fb7a0356452c1bb94dbabaf0582930/triton-3.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47bc87ad66fa4ef17968299acacecaab71ce40a238890acc6ad197c3abe2b8f1", size = 156516468 }, - { url = "https://files.pythonhosted.org/packages/0a/93/f28a696fa750b9b608baa236f8225dd3290e5aff27433b06143adc025961/triton-3.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce4700fc14032af1e049005ae94ba908e71cd6c2df682239aed08e49bc71b742", size = 156580729 }, -] - -[[package]] +version = "3.5.0" +marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/22/507b6f58a35e05e84381630b2dc2a3cee1a7a2a7eaf4cba857c638a18a24/triton-3.5.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T19:15:43Z, size = 159827599, hashes = { sha256 = "6f90de6a6566bb619b4c0adc9855729e1b1b5e26533fca1bf6206e96b6d277a3" } }, + { url = "https://files.pythonhosted.org/packages/0b/eb/09e31d107a5d00eb281aa7e6635ca463e9bca86515944e399480eadb71f8/triton-3.5.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-13T16:37:49Z, size = 170333110, hashes = { sha256 = "d5d3b3d480debf24eaa739623c9a42446b0b77f95593d30eb1f64cd2278cc1f0" } }, + { url = "https://files.pythonhosted.org/packages/79/f9/b6f60f978397c616fd8dacca2305759fe4f80d397b20ef72534803244bd5/triton-3.5.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T19:15:49Z, size = 159926731, hashes = { sha256 = "8457b22148defefdcb7fa8144b05ce211b9faefad650a1ce85b23df488d5549c" } }, + { url = "https://files.pythonhosted.org/packages/3d/78/949a04391c21956c816523678f0e5fa308eb5b1e7622d88c4e4ef5fceca0/triton-3.5.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-13T16:37:57Z, size = 170433488, hashes = { sha256 = "f34bfa21c5b3a203c0f0eab28dcc1e49bd1f67d22724e77fb6665a659200a4ec" } }, + { url = "https://files.pythonhosted.org/packages/87/9b/30988039e1e84df7554fba24e6a734d2d0e847af33cabdf9b532b3c51456/triton-3.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T19:15:56Z, size = 159946647, hashes = { sha256 = "7da21fccceafc163e3a5e857abe34351ef76345af06cabf9637a914742671f0b" } }, + { url = "https://files.pythonhosted.org/packages/f5/3a/e991574f3102147b642e49637e0281e9bb7c4ba254edb2bab78247c85e01/triton-3.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-13T16:38:05Z, size = 170476535, hashes = { sha256 = "c9e71db82261c4ffa3921cd050cd5faa18322d2d405c30eb56084afaff3b0833" } }, + { url = "https://files.pythonhosted.org/packages/cd/85/e37f1197acb04c8f3d83851d23d5d6ed5060ef74580668b112e23fdfa203/triton-3.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T19:16:01Z, size = 159958970, hashes = { sha256 = "188da5b81fa2f8322c27fec1627703eac24cb9bb7ab0dfbe9925973bc1b070d3" } }, + { url = "https://files.pythonhosted.org/packages/6c/29/10728de8a6e932e517c10773486b8e99f85d1b1d9dd87d9a9616e1fef4a1/triton-3.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-13T16:38:11Z, size = 170487289, hashes = { sha256 = "e6bb9aa5519c084a333acdba443789e50012a4b851cd486c54f0b8dc2a8d3a12" } }, + { url = "https://files.pythonhosted.org/packages/b8/1d/38258f05010ac17a7b058c022911c9cae6526e149b7397134a048cf5a6c2/triton-3.5.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T19:16:07Z, size = 160073012, hashes = { sha256 = "03127d9b33aaf979c856676b394bc059ec1d68cb6da68ae03f62dd8ad77a04ae" } }, + { url = "https://files.pythonhosted.org/packages/5c/38/db80e48b9220c9bce872b0f616ad0446cdf554a40b85c7865cbca99ab3c2/triton-3.5.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-13T16:38:17Z, size = 170577179, hashes = { sha256 = "c83f2343e1a220a716c7b3ab9fccfcbe3ad4020d189549200e2d2e8d5868bed9" } }, + { url = "https://files.pythonhosted.org/packages/91/fe/8f5771d00227f4eb1ee034f218ed427102b989366d2275fe3b3c105a3921/triton-3.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T19:16:12Z, size = 159957460, hashes = { sha256 = "468936651d383f4a6d10068d34a627505e13af55be5d002b9f27b987e7a5f0ac" } }, + { url = "https://files.pythonhosted.org/packages/ff/60/1810655d1d856c9a4fcc90ee8966d85f552d98c53a6589f95ab2cbe27bb8/triton-3.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-13T16:38:24Z, size = 170487949, hashes = { sha256 = "da0fa67ccd76c3dcfb0bffe1b1c57c685136a6bd33d141c24d9655d4185b1289" } }, + { url = "https://files.pythonhosted.org/packages/78/59/99edd103958fe6e42b50b9ad8ce4f223ddf4ccf475259cf7d2b53381dc6c/triton-3.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T19:16:18Z, size = 160075629, hashes = { sha256 = "c7ceef21410229ac23173a28eee5cfc0e37c1dfdb8b4bc11ecda2e3ecec7c686" } }, + { url = "https://files.pythonhosted.org/packages/fb/b7/1dec8433ac604c061173d0589d99217fe7bf90a70bdc375e745d044b8aad/triton-3.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-13T16:38:31Z, size = 170580176, hashes = { sha256 = "317fe477ea8fd4524a6a8c499fb0a36984a56d0b75bf9c9cb6133a1c56d5a6e7" } }, +] + +[[packages]] +name = "typeguard" +version = "4.4.4" +sdist = { url = "https://files.pythonhosted.org/packages/c7/68/71c1a15b5f65f40e91b65da23b8224dad41349894535a97f63a52e462196/typeguard-4.4.4.tar.gz", upload-time = 2025-06-18T09:56:07Z, size = 75203, hashes = { sha256 = "3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl", upload-time = 2025-06-18T09:56:05Z, size = 34874, hashes = { sha256 = "b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e" } }] + +[[packages]] name = "typing-extensions" -version = "4.13.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, -] +version = "4.15.0" +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", upload-time = 2025-08-25T13:49:26Z, size = 109391, hashes = { sha256 = "0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", upload-time = 2025-08-25T13:49:24Z, size = 44614, hashes = { sha256 = "f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" } }] -[[package]] +[[packages]] name = "typing-inspection" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, -] +version = "0.4.2" +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", upload-time = 2025-10-01T02:14:41Z, size = 75949, hashes = { sha256 = "ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", upload-time = 2025-10-01T02:14:40Z, size = 14611, hashes = { sha256 = "4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7" } }] -[[package]] +[[packages]] name = "tzdata" version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, -] +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", upload-time = 2025-03-23T13:54:43Z, size = 196380, hashes = { sha256 = "b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", upload-time = 2025-03-23T13:54:41Z, size = 347839, hashes = { sha256 = "1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8" } }] -[[package]] +[[packages]] name = "urllib3" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, -] - -[[package]] +version = "2.5.0" +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", upload-time = 2025-06-18T14:07:41Z, size = 393185, hashes = { sha256 = "3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", upload-time = 2025-06-18T14:07:40Z, size = 129795, hashes = { sha256 = "e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" } }] + +[[packages]] +name = "uvicorn" +version = "0.37.0" +sdist = { url = "https://files.pythonhosted.org/packages/71/57/1616c8274c3442d802621abf5deb230771c7a0fec9414cb6763900eb3868/uvicorn-0.37.0.tar.gz", upload-time = 2025-09-23T13:33:47Z, size = 80367, hashes = { sha256 = "4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/85/cd/584a2ceb5532af99dd09e50919e3615ba99aa127e9850eafe5f31ddfdb9a/uvicorn-0.37.0-py3-none-any.whl", upload-time = 2025-09-23T13:33:45Z, size = 67976, hashes = { sha256 = "913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c" } }] + +[[packages]] +name = "uvloop" +version = "0.21.0" +marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", upload-time = 2024-10-14T23:38:35Z, size = 2492741, hashes = { sha256 = "3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/76/44a55515e8c9505aa1420aebacf4dd82552e5e15691654894e90d0bd051a/uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", upload-time = 2024-10-14T23:37:20Z, size = 1442019, hashes = { sha256 = "ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f" } }, + { url = "https://files.pythonhosted.org/packages/35/5a/62d5800358a78cc25c8a6c72ef8b10851bdb8cca22e14d9c74167b7f86da/uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", upload-time = 2024-10-14T23:37:22Z, size = 801898, hashes = { sha256 = "196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d" } }, + { url = "https://files.pythonhosted.org/packages/f3/96/63695e0ebd7da6c741ccd4489b5947394435e198a1382349c17b1146bb97/uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2024-10-14T23:37:25Z, size = 3827735, hashes = { sha256 = "f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26" } }, + { url = "https://files.pythonhosted.org/packages/61/e0/f0f8ec84979068ffae132c58c79af1de9cceeb664076beea86d941af1a30/uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-10-14T23:37:27Z, size = 3825126, hashes = { sha256 = "87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb" } }, + { url = "https://files.pythonhosted.org/packages/bf/fe/5e94a977d058a54a19df95f12f7161ab6e323ad49f4dabc28822eb2df7ea/uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2024-10-14T23:37:29Z, size = 3705789, hashes = { sha256 = "10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f" } }, + { url = "https://files.pythonhosted.org/packages/26/dd/c7179618e46092a77e036650c1f056041a028a35c4d76945089fcfc38af8/uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2024-10-14T23:37:32Z, size = 3800523, hashes = { sha256 = "67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c" } }, + { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", upload-time = 2024-10-14T23:37:33Z, size = 1447410, hashes = { sha256 = "c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8" } }, + { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", upload-time = 2024-10-14T23:37:36Z, size = 805476, hashes = { sha256 = "0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0" } }, + { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2024-10-14T23:37:37Z, size = 3960855, hashes = { sha256 = "b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e" } }, + { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-10-14T23:37:40Z, size = 3973185, hashes = { sha256 = "8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb" } }, + { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2024-10-14T23:37:42Z, size = 3820256, hashes = { sha256 = "baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6" } }, + { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2024-10-14T23:37:45Z, size = 3937323, hashes = { sha256 = "4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d" } }, + { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", upload-time = 2024-10-14T23:37:47Z, size = 1471284, hashes = { sha256 = "359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c" } }, + { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2024-10-14T23:37:50Z, size = 821349, hashes = { sha256 = "f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2" } }, + { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2024-10-14T23:37:51Z, size = 4580089, hashes = { sha256 = "baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d" } }, + { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-10-14T23:37:54Z, size = 4693770, hashes = { sha256 = "86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc" } }, + { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2024-10-14T23:37:55Z, size = 4451321, hashes = { sha256 = "461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb" } }, + { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2024-10-14T23:37:58Z, size = 4659022, hashes = { sha256 = "183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f" } }, + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", upload-time = 2024-10-14T23:38:00Z, size = 1468123, hashes = { sha256 = "bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281" } }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2024-10-14T23:38:02Z, size = 819325, hashes = { sha256 = "787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af" } }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2024-10-14T23:38:04Z, size = 4582806, hashes = { sha256 = "5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6" } }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-10-14T23:38:06Z, size = 4701068, hashes = { sha256 = "f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816" } }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2024-10-14T23:38:08Z, size = 4454428, hashes = { sha256 = "bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc" } }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2024-10-14T23:38:10Z, size = 4660018, hashes = { sha256 = "a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553" } }, + { url = "https://files.pythonhosted.org/packages/b5/7b/85a2c8231eac451ef9caecba8715295820c9f94fb51c4f5b2e39c79a5c11/uvloop-0.21.0-cp38-cp38-macosx_10_9_universal2.whl", upload-time = 2024-10-14T23:38:12Z, size = 1433814, hashes = { sha256 = "17df489689befc72c39a08359efac29bbee8eee5209650d4b9f34df73d22e414" } }, + { url = "https://files.pythonhosted.org/packages/78/c9/10272e791562be6cfc4ee127883087de6443fede8f010b019ca0fdf841c1/uvloop-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", upload-time = 2024-10-14T23:38:15Z, size = 797954, hashes = { sha256 = "bc09f0ff191e61c2d592a752423c767b4ebb2986daa9ed62908e2b1b9a9ae206" } }, + { url = "https://files.pythonhosted.org/packages/62/23/29da7a6d3fba8dfe375ea48a8c3a3e5562b770d24008d79a7a6e0150d7c1/uvloop-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2024-10-14T23:38:16Z, size = 4302867, hashes = { sha256 = "f0ce1b49560b1d2d8a2977e3ba4afb2414fb46b86a1b64056bc4ab929efdafbe" } }, + { url = "https://files.pythonhosted.org/packages/9a/46/72fb3fbb457cd68632542ecc7fa191a17dac501f70b7f3786a18912bbe0e/uvloop-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-10-14T23:38:19Z, size = 4303228, hashes = { sha256 = "e678ad6fe52af2c58d2ae3c73dc85524ba8abe637f134bf3564ed07f555c5e79" } }, + { url = "https://files.pythonhosted.org/packages/1a/80/3f57f2458460501b709aec7c7e7f303b81b38ca35f786b41bf402b3349e8/uvloop-0.21.0-cp38-cp38-musllinux_1_2_aarch64.whl", upload-time = 2024-10-14T23:38:21Z, size = 4163776, hashes = { sha256 = "460def4412e473896ef179a1671b40c039c7012184b627898eea5072ef6f017a" } }, + { url = "https://files.pythonhosted.org/packages/4f/9f/07c88dd3e76171e7808ff63719af12ee8bb6ea56fe40ea274da606ae5ade/uvloop-0.21.0-cp38-cp38-musllinux_1_2_x86_64.whl", upload-time = 2024-10-14T23:38:23Z, size = 4292873, hashes = { sha256 = "10da8046cc4a8f12c91a1c39d1dd1585c41162a15caaef165c2174db9ef18bdc" } }, + { url = "https://files.pythonhosted.org/packages/3c/a4/646a9d0edff7cde25fc1734695d3dfcee0501140dd0e723e4df3f0a50acb/uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", upload-time = 2024-10-14T23:38:24Z, size = 1439646, hashes = { sha256 = "c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b" } }, + { url = "https://files.pythonhosted.org/packages/01/2e/e128c66106af9728f86ebfeeb52af27ecd3cb09336f3e2f3e06053707a15/uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", upload-time = 2024-10-14T23:38:26Z, size = 800931, hashes = { sha256 = "46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2" } }, + { url = "https://files.pythonhosted.org/packages/2d/1a/9fbc2b1543d0df11f7aed1632f64bdf5ecc4053cf98cdc9edb91a65494f9/uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2024-10-14T23:38:27Z, size = 3829660, hashes = { sha256 = "53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0" } }, + { url = "https://files.pythonhosted.org/packages/b8/c0/392e235e4100ae3b95b5c6dac77f82b529d2760942b1e7e0981e5d8e895d/uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-10-14T23:38:29Z, size = 3827185, hashes = { sha256 = "88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75" } }, + { url = "https://files.pythonhosted.org/packages/e1/24/a5da6aba58f99aed5255eca87d58d1760853e8302d390820cc29058408e3/uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", upload-time = 2024-10-14T23:38:31Z, size = 3705833, hashes = { sha256 = "221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd" } }, + { url = "https://files.pythonhosted.org/packages/1a/5c/6ba221bb60f1e6474474102e17e38612ec7a06dc320e22b687ab563d877f/uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", upload-time = 2024-10-14T23:38:33Z, size = 3804696, hashes = { sha256 = "2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff" } }, +] + +[[packages]] name = "vine" version = "5.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636 }, -] - -[[package]] +sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", upload-time = 2023-11-05T08:46:53Z, size = 48980, hashes = { sha256 = "8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", upload-time = 2023-11-05T08:46:51Z, size = 9636, hashes = { sha256 = "40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc" } }] + +[[packages]] +name = "watchdog" +version = "6.0.0" +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", upload-time = 2024-11-01T14:07:13Z, size = 131220, hashes = { sha256 = "9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", upload-time = 2024-11-01T14:06:24Z, size = 96390, hashes = { sha256 = "d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26" } }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", upload-time = 2024-11-01T14:06:27Z, size = 88389, hashes = { sha256 = "bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112" } }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2024-11-01T14:06:29Z, size = 89020, hashes = { sha256 = "c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3" } }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", upload-time = 2024-11-01T14:06:31Z, size = 96393, hashes = { sha256 = "6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c" } }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", upload-time = 2024-11-01T14:06:32Z, size = 88392, hashes = { sha256 = "ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2" } }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2024-11-01T14:06:34Z, size = 89019, hashes = { sha256 = "afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c" } }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", upload-time = 2024-11-01T14:06:37Z, size = 96471, hashes = { sha256 = "bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948" } }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2024-11-01T14:06:39Z, size = 88449, hashes = { sha256 = "c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860" } }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2024-11-01T14:06:41Z, size = 89054, hashes = { sha256 = "6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0" } }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", upload-time = 2024-11-01T14:06:42Z, size = 96480, hashes = { sha256 = "490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c" } }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2024-11-01T14:06:45Z, size = 88451, hashes = { sha256 = "76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134" } }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2024-11-01T14:06:47Z, size = 89057, hashes = { sha256 = "a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b" } }, + { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", upload-time = 2024-11-01T14:06:49Z, size = 96390, hashes = { sha256 = "e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8" } }, + { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", upload-time = 2024-11-01T14:06:50Z, size = 88386, hashes = { sha256 = "90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a" } }, + { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2024-11-01T14:06:51Z, size = 89017, hashes = { sha256 = "e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c" } }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", upload-time = 2024-11-01T14:06:53Z, size = 87902, hashes = { sha256 = "c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881" } }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", upload-time = 2024-11-01T14:06:55Z, size = 88380, hashes = { sha256 = "9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11" } }, + { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", upload-time = 2024-11-01T14:06:57Z, size = 87903, hashes = { sha256 = "7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa" } }, + { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", upload-time = 2024-11-01T14:06:58Z, size = 88381, hashes = { sha256 = "e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e" } }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", upload-time = 2024-11-01T14:06:59Z, size = 79079, hashes = { sha256 = "7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13" } }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", upload-time = 2024-11-01T14:07:01Z, size = 79078, hashes = { sha256 = "9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379" } }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", upload-time = 2024-11-01T14:07:02Z, size = 79076, hashes = { sha256 = "82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e" } }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", upload-time = 2024-11-01T14:07:03Z, size = 79077, hashes = { sha256 = "212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f" } }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", upload-time = 2024-11-01T14:07:05Z, size = 79078, hashes = { sha256 = "e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26" } }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", upload-time = 2024-11-01T14:07:06Z, size = 79077, hashes = { sha256 = "2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c" } }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", upload-time = 2024-11-01T14:07:07Z, size = 79078, hashes = { sha256 = "20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2" } }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", upload-time = 2024-11-01T14:07:09Z, size = 79065, hashes = { sha256 = "07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a" } }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", upload-time = 2024-11-01T14:07:10Z, size = 79070, hashes = { sha256 = "cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680" } }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", upload-time = 2024-11-01T14:07:11Z, size = 79067, hashes = { sha256 = "a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f" } }, +] + +[[packages]] +name = "watchfiles" +version = "1.1.1" +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", upload-time = 2025-10-14T15:06:21Z, size = 94440, hashes = { sha256 = "a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T15:04:18Z, size = 407318, hashes = { sha256 = "eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c" } }, + { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-10-14T15:04:20Z, size = 394478, hashes = { sha256 = "03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43" } }, + { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T15:04:21Z, size = 449894, hashes = { sha256 = "8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31" } }, + { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T15:04:22Z, size = 459065, hashes = { sha256 = "f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac" } }, + { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-10-14T15:04:24Z, size = 488377, hashes = { sha256 = "3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d" } }, + { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T15:04:25Z, size = 595837, hashes = { sha256 = "e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d" } }, + { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T15:04:26Z, size = 473456, hashes = { sha256 = "620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863" } }, + { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T15:04:27Z, size = 455614, hashes = { sha256 = "544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab" } }, + { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T15:04:28Z, size = 630690, hashes = { sha256 = "bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82" } }, + { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T15:04:29Z, size = 622459, hashes = { sha256 = "1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4" } }, + { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", upload-time = 2025-10-14T15:04:30Z, size = 272663, hashes = { sha256 = "3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844" } }, + { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", upload-time = 2025-10-14T15:04:31Z, size = 287453, hashes = { sha256 = "a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e" } }, + { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T15:04:32Z, size = 406529, hashes = { sha256 = "f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5" } }, + { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-10-14T15:04:33Z, size = 394384, hashes = { sha256 = "421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741" } }, + { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T15:04:34Z, size = 448789, hashes = { sha256 = "6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6" } }, + { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T15:04:35Z, size = 460521, hashes = { sha256 = "f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b" } }, + { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-10-14T15:04:37Z, size = 488722, hashes = { sha256 = "b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14" } }, + { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T15:04:38Z, size = 596088, hashes = { sha256 = "5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d" } }, + { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T15:04:39Z, size = 472923, hashes = { sha256 = "9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff" } }, + { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T15:04:40Z, size = 456080, hashes = { sha256 = "aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606" } }, + { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T15:04:41Z, size = 629432, hashes = { sha256 = "5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701" } }, + { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T15:04:42Z, size = 623046, hashes = { sha256 = "399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10" } }, + { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", upload-time = 2025-10-14T15:04:43Z, size = 271473, hashes = { sha256 = "de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849" } }, + { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", upload-time = 2025-10-14T15:04:44Z, size = 287598, hashes = { sha256 = "35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4" } }, + { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", upload-time = 2025-10-14T15:04:45Z, size = 277210, hashes = { sha256 = "57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e" } }, + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T15:04:46Z, size = 404745, hashes = { sha256 = "8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d" } }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-10-14T15:04:48Z, size = 391769, hashes = { sha256 = "bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610" } }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T15:04:49Z, size = 449374, hashes = { sha256 = "8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af" } }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T15:04:50Z, size = 459485, hashes = { sha256 = "2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6" } }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-10-14T15:04:51Z, size = 488813, hashes = { sha256 = "30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce" } }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T15:04:52Z, size = 594816, hashes = { sha256 = "f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa" } }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T15:04:53Z, size = 475186, hashes = { sha256 = "dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb" } }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T15:04:55Z, size = 456812, hashes = { sha256 = "1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803" } }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T15:04:56Z, size = 630196, hashes = { sha256 = "28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94" } }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T15:04:57Z, size = 622657, hashes = { sha256 = "36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43" } }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", upload-time = 2025-10-14T15:04:59Z, size = 272042, hashes = { sha256 = "859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9" } }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", upload-time = 2025-10-14T15:05:00Z, size = 288410, hashes = { sha256 = "91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9" } }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", upload-time = 2025-10-14T15:05:01Z, size = 278209, hashes = { sha256 = "a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404" } }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T15:05:02Z, size = 404321, hashes = { sha256 = "130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18" } }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-10-14T15:05:03Z, size = 391783, hashes = { sha256 = "5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a" } }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T15:05:04Z, size = 449279, hashes = { sha256 = "14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219" } }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T15:05:04Z, size = 459405, hashes = { sha256 = "f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428" } }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-10-14T15:05:05Z, size = 488976, hashes = { sha256 = "059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0" } }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T15:05:06Z, size = 595506, hashes = { sha256 = "bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150" } }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T15:05:07Z, size = 474936, hashes = { sha256 = "319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae" } }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T15:05:09Z, size = 456147, hashes = { sha256 = "c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d" } }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T15:05:10Z, size = 630007, hashes = { sha256 = "c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b" } }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T15:05:11Z, size = 622280, hashes = { sha256 = "3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374" } }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", upload-time = 2025-10-14T15:05:12Z, size = 272056, hashes = { sha256 = "bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0" } }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", upload-time = 2025-10-14T15:05:13Z, size = 288162, hashes = { sha256 = "52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42" } }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", upload-time = 2025-10-14T15:05:14Z, size = 277909, hashes = { sha256 = "ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18" } }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T15:05:15Z, size = 403389, hashes = { sha256 = "563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da" } }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-10-14T15:05:16Z, size = 389964, hashes = { sha256 = "3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051" } }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T15:05:17Z, size = 448114, hashes = { sha256 = "ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e" } }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T15:05:18Z, size = 460264, hashes = { sha256 = "cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70" } }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-10-14T15:05:20Z, size = 487877, hashes = { sha256 = "836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261" } }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T15:05:21Z, size = 595176, hashes = { sha256 = "743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620" } }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T15:05:22Z, size = 473577, hashes = { sha256 = "afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04" } }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T15:05:23Z, size = 455425, hashes = { sha256 = "3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77" } }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T15:05:24Z, size = 628826, hashes = { sha256 = "831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef" } }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T15:05:25Z, size = 622208, hashes = { sha256 = "f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf" } }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T15:05:26Z, size = 404315, hashes = { sha256 = "d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5" } }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", upload-time = 2025-10-14T15:05:27Z, size = 390869, hashes = { sha256 = "39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd" } }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T15:05:28Z, size = 449919, hashes = { sha256 = "7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb" } }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T15:05:30Z, size = 460845, hashes = { sha256 = "bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5" } }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-10-14T15:05:31Z, size = 489027, hashes = { sha256 = "b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3" } }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T15:05:32Z, size = 595615, hashes = { sha256 = "526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33" } }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T15:05:33Z, size = 474836, hashes = { sha256 = "04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510" } }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T15:05:34Z, size = 455099, hashes = { sha256 = "5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05" } }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T15:05:35Z, size = 630626, hashes = { sha256 = "74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6" } }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T15:05:36Z, size = 622519, hashes = { sha256 = "8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81" } }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", upload-time = 2025-10-14T15:05:37Z, size = 272078, hashes = { sha256 = "3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b" } }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", upload-time = 2025-10-14T15:05:38Z, size = 287664, hashes = { sha256 = "c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a" } }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", upload-time = 2025-10-14T15:05:39Z, size = 277154, hashes = { sha256 = "842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02" } }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T15:05:40Z, size = 403820, hashes = { sha256 = "88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21" } }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", upload-time = 2025-10-14T15:05:41Z, size = 390510, hashes = { sha256 = "55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5" } }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T15:05:43Z, size = 448408, hashes = { sha256 = "3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7" } }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T15:05:44Z, size = 458968, hashes = { sha256 = "6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101" } }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-10-14T15:05:45Z, size = 488096, hashes = { sha256 = "f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44" } }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T15:05:46Z, size = 596040, hashes = { sha256 = "00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c" } }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T15:05:47Z, size = 473847, hashes = { sha256 = "a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc" } }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T15:05:48Z, size = 455072, hashes = { sha256 = "8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c" } }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T15:05:49Z, size = 629104, hashes = { sha256 = "311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099" } }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T15:05:50Z, size = 622112, hashes = { sha256 = "a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01" } }, + { url = "https://files.pythonhosted.org/packages/a4/68/a7303a15cc797ab04d58f1fea7f67c50bd7f80090dfd7e750e7576e07582/watchfiles-1.1.1-cp39-cp39-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T15:05:51Z, size = 409220, hashes = { sha256 = "c882d69f6903ef6092bedfb7be973d9319940d56b8427ab9187d1ecd73438a70" } }, + { url = "https://files.pythonhosted.org/packages/99/b8/d1857ce9ac76034c053fa7ef0e0ef92d8bd031e842ea6f5171725d31e88f/watchfiles-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2025-10-14T15:05:53Z, size = 396712, hashes = { sha256 = "d6ff426a7cb54f310d51bfe83fe9f2bbe40d540c741dc974ebc30e6aa238f52e" } }, + { url = "https://files.pythonhosted.org/packages/41/7a/da7ada566f48beaa6a30b13335b49d1f6febaf3a5ddbd1d92163a1002cf4/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T15:05:54Z, size = 451462, hashes = { sha256 = "79ff6c6eadf2e3fc0d7786331362e6ef1e51125892c75f1004bd6b52155fb956" } }, + { url = "https://files.pythonhosted.org/packages/e2/b2/7cb9e0d5445a8d45c4cccd68a590d9e3a453289366b96ff37d1075aaebef/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", upload-time = 2025-10-14T15:05:55Z, size = 460811, hashes = { sha256 = "c1f5210f1b8fc91ead1283c6fd89f70e76fb07283ec738056cf34d51e9c1d62c" } }, + { url = "https://files.pythonhosted.org/packages/04/9d/b07d4491dde6db6ea6c680fdec452f4be363d65c82004faf2d853f59b76f/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-10-14T15:05:56Z, size = 490576, hashes = { sha256 = "b9c4702f29ca48e023ffd9b7ff6b822acdf47cb1ff44cb490a3f1d5ec8987e9c" } }, + { url = "https://files.pythonhosted.org/packages/56/03/e64dcab0a1806157db272a61b7891b062f441a30580a581ae72114259472/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", upload-time = 2025-10-14T15:05:57Z, size = 597726, hashes = { sha256 = "acb08650863767cbc58bca4813b92df4d6c648459dcaa3d4155681962b2aa2d3" } }, + { url = "https://files.pythonhosted.org/packages/5c/8e/a827cf4a8d5f2903a19a934dcf512082eb07675253e154d4cd9367978a58/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", upload-time = 2025-10-14T15:05:59Z, size = 474900, hashes = { sha256 = "08af70fd77eee58549cd69c25055dc344f918d992ff626068242259f98d598a2" } }, + { url = "https://files.pythonhosted.org/packages/dc/a6/94fed0b346b85b22303a12eee5f431006fae6af70d841cac2f4403245533/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T15:06:00Z, size = 457521, hashes = { sha256 = "6c3631058c37e4a0ec440bf583bc53cdbd13e5661bb6f465bc1d88ee9a0a4d02" } }, + { url = "https://files.pythonhosted.org/packages/c4/64/bc3331150e8f3c778d48a4615d4b72b3d2d87868635e6c54bbd924946189/watchfiles-1.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", upload-time = 2025-10-14T15:06:01Z, size = 632191, hashes = { sha256 = "cf57a27fb986c6243d2ee78392c503826056ffe0287e8794503b10fb51b881be" } }, + { url = "https://files.pythonhosted.org/packages/e4/84/f39e19549c2f3ec97225dcb2ceb9a7bb3c5004ed227aad1f321bf0ff2051/watchfiles-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", upload-time = 2025-10-14T15:06:02Z, size = 623923, hashes = { sha256 = "d7e7067c98040d646982daa1f37a33d3544138ea155536c2e0e63e07ff8a7e0f" } }, + { url = "https://files.pythonhosted.org/packages/0e/24/0759ae15d9a0c9c5fe946bd4cf45ab9e7bad7cfede2c06dc10f59171b29f/watchfiles-1.1.1-cp39-cp39-win32.whl", upload-time = 2025-10-14T15:06:03Z, size = 274010, hashes = { sha256 = "6c9c9262f454d1c4d8aaa7050121eb4f3aea197360553699520767daebf2180b" } }, + { url = "https://files.pythonhosted.org/packages/7e/3b/eb26cddd4dfa081e2bf6918be3b2fc05ee3b55c1d21331d5562ee0c6aaad/watchfiles-1.1.1-cp39-cp39-win_amd64.whl", upload-time = 2025-10-14T15:06:04Z, size = 289090, hashes = { sha256 = "74472234c8370669850e1c312490f6026d132ca2d396abfad8830b4f1c096957" } }, + { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T15:06:05Z, size = 409611, hashes = { sha256 = "17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3" } }, + { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", upload-time = 2025-10-14T15:06:07Z, size = 396889, hashes = { sha256 = "672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2" } }, + { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T15:06:08Z, size = 451616, hashes = { sha256 = "77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d" } }, + { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T15:06:09Z, size = 458413, hashes = { sha256 = "0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b" } }, + { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T15:06:10Z, size = 408250, hashes = { sha256 = "db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88" } }, + { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", upload-time = 2025-10-14T15:06:11Z, size = 396117, hashes = { sha256 = "89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336" } }, + { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T15:06:12Z, size = 450493, hashes = { sha256 = "ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24" } }, + { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T15:06:13Z, size = 457546, hashes = { sha256 = "3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49" } }, + { url = "https://files.pythonhosted.org/packages/00/db/38a2c52fdbbfe2fc7ffaaaaaebc927d52b9f4d5139bba3186c19a7463001/watchfiles-1.1.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", upload-time = 2025-10-14T15:06:14Z, size = 409210, hashes = { sha256 = "cdab464fee731e0884c35ae3588514a9bcf718d0e2c82169c1c4a85cc19c3c7f" } }, + { url = "https://files.pythonhosted.org/packages/d1/43/d7e8b71f6c21ff813ee8da1006f89b6c7fff047fb4c8b16ceb5e840599c5/watchfiles-1.1.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", upload-time = 2025-10-14T15:06:16Z, size = 397286, hashes = { sha256 = "3dbd8cbadd46984f802f6d479b7e3afa86c42d13e8f0f322d669d79722c8ec34" } }, + { url = "https://files.pythonhosted.org/packages/1f/5d/884074a5269317e75bd0b915644b702b89de73e61a8a7446e2b225f45b1f/watchfiles-1.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-10-14T15:06:18Z, size = 451768, hashes = { sha256 = "5524298e3827105b61951a29c3512deb9578586abf3a7c5da4a8069df247cccc" } }, + { url = "https://files.pythonhosted.org/packages/17/71/7ffcaa9b5e8961a25026058058c62ec8f604d2a6e8e1e94bee8a09e1593f/watchfiles-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-10-14T15:06:19Z, size = 458561, hashes = { sha256 = "4b943d3668d61cfa528eb949577479d3b077fd25fb83c641235437bc0b5bc60e" } }, +] + +[[packages]] name = "wcwidth" -version = "0.2.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, -] +version = "0.2.14" +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", upload-time = 2025-09-22T16:29:53Z, size = 102293, hashes = { sha256 = "4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", upload-time = 2025-09-22T16:29:51Z, size = 37286, hashes = { sha256 = "a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1" } }] + +[[packages]] +name = "websockets" +version = "15.0.1" +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", upload-time = 2025-03-05T20:03:41Z, size = 177016, hashes = { sha256 = "82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", upload-time = 2025-03-05T20:01:35Z, size = 175423, hashes = { sha256 = "d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b" } }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", upload-time = 2025-03-05T20:01:37Z, size = 173080, hashes = { sha256 = "ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205" } }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", upload-time = 2025-03-05T20:01:39Z, size = 173329, hashes = { sha256 = "5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a" } }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-03-05T20:01:41Z, size = 182312, hashes = { sha256 = "0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e" } }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-03-05T20:01:43Z, size = 181319, hashes = { sha256 = "4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf" } }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-03-05T20:01:46Z, size = 181631, hashes = { sha256 = "ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb" } }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", upload-time = 2025-03-05T20:01:47Z, size = 182016, hashes = { sha256 = "5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d" } }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", upload-time = 2025-03-05T20:01:48Z, size = 181426, hashes = { sha256 = "0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9" } }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", upload-time = 2025-03-05T20:01:50Z, size = 181360, hashes = { sha256 = "3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c" } }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", upload-time = 2025-03-05T20:01:52Z, size = 176388, hashes = { sha256 = "1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256" } }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", upload-time = 2025-03-05T20:01:53Z, size = 176830, hashes = { sha256 = "39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41" } }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", upload-time = 2025-03-05T20:01:56Z, size = 175423, hashes = { sha256 = "823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431" } }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", upload-time = 2025-03-05T20:01:57Z, size = 173082, hashes = { sha256 = "678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57" } }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", upload-time = 2025-03-05T20:01:59Z, size = 173330, hashes = { sha256 = "d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905" } }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-03-05T20:02:00Z, size = 182878, hashes = { sha256 = "d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562" } }, + { 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", upload-time = 2025-03-05T20:02:03Z, size = 181883, hashes = { sha256 = "66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792" } }, + { 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", upload-time = 2025-03-05T20:02:05Z, size = 182252, hashes = { sha256 = "8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413" } }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", upload-time = 2025-03-05T20:02:07Z, size = 182521, hashes = { sha256 = "8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8" } }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", upload-time = 2025-03-05T20:02:09Z, size = 181958, hashes = { sha256 = "693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3" } }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", upload-time = 2025-03-05T20:02:11Z, size = 181918, hashes = { sha256 = "54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf" } }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", upload-time = 2025-03-05T20:02:13Z, size = 176388, hashes = { sha256 = "16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85" } }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", upload-time = 2025-03-05T20:02:14Z, size = 176828, hashes = { sha256 = "27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065" } }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", upload-time = 2025-03-05T20:02:16Z, size = 175437, hashes = { sha256 = "3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3" } }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", upload-time = 2025-03-05T20:02:18Z, size = 173096, hashes = { sha256 = "592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665" } }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", upload-time = 2025-03-05T20:02:20Z, size = 173332, hashes = { sha256 = "0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2" } }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-03-05T20:02:22Z, size = 183152, hashes = { sha256 = "e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215" } }, + { 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", upload-time = 2025-03-05T20:02:24Z, size = 182096, hashes = { sha256 = "0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5" } }, + { 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", upload-time = 2025-03-05T20:02:25Z, size = 182523, hashes = { sha256 = "64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65" } }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2025-03-05T20:02:26Z, size = 182790, hashes = { sha256 = "d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe" } }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", upload-time = 2025-03-05T20:02:30Z, size = 182165, hashes = { sha256 = "5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4" } }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2025-03-05T20:02:31Z, size = 182160, hashes = { sha256 = "3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597" } }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", upload-time = 2025-03-05T20:02:33Z, size = 176395, hashes = { sha256 = "c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9" } }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", upload-time = 2025-03-05T20:02:34Z, size = 176841, hashes = { sha256 = "fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7" } }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", upload-time = 2025-03-05T20:02:36Z, size = 175440, hashes = { sha256 = "ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931" } }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-03-05T20:02:37Z, size = 173098, hashes = { sha256 = "5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675" } }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-03-05T20:02:39Z, size = 173329, hashes = { sha256 = "746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151" } }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-03-05T20:02:40Z, size = 183111, hashes = { sha256 = "595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22" } }, + { 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", upload-time = 2025-03-05T20:02:41Z, size = 182054, hashes = { sha256 = "3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f" } }, + { 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", upload-time = 2025-03-05T20:02:43Z, size = 182496, hashes = { sha256 = "0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8" } }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-03-05T20:02:48Z, size = 182829, hashes = { sha256 = "229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375" } }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", upload-time = 2025-03-05T20:02:50Z, size = 182217, hashes = { sha256 = "756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d" } }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-03-05T20:02:51Z, size = 182195, hashes = { sha256 = "558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4" } }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", upload-time = 2025-03-05T20:02:53Z, size = 176393, hashes = { sha256 = "ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa" } }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", upload-time = 2025-03-05T20:02:55Z, size = 176837, hashes = { sha256 = "e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561" } }, + { url = "https://files.pythonhosted.org/packages/36/db/3fff0bcbe339a6fa6a3b9e3fbc2bfb321ec2f4cd233692272c5a8d6cf801/websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", upload-time = 2025-03-05T20:02:56Z, size = 175424, hashes = { sha256 = "5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5" } }, + { url = "https://files.pythonhosted.org/packages/46/e6/519054c2f477def4165b0ec060ad664ed174e140b0d1cbb9fafa4a54f6db/websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", upload-time = 2025-03-05T20:02:58Z, size = 173077, hashes = { sha256 = "abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a" } }, + { url = "https://files.pythonhosted.org/packages/1a/21/c0712e382df64c93a0d16449ecbf87b647163485ca1cc3f6cbadb36d2b03/websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", upload-time = 2025-03-05T20:02:59Z, size = 173324, hashes = { sha256 = "a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b" } }, + { url = "https://files.pythonhosted.org/packages/1c/cb/51ba82e59b3a664df54beed8ad95517c1b4dc1a913730e7a7db778f21291/websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-03-05T20:03:01Z, size = 182094, hashes = { sha256 = "d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770" } }, + { url = "https://files.pythonhosted.org/packages/fb/0f/bf3788c03fec679bcdaef787518dbe60d12fe5615a544a6d4cf82f045193/websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-03-05T20:03:03Z, size = 181094, hashes = { sha256 = "47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb" } }, + { url = "https://files.pythonhosted.org/packages/5e/da/9fb8c21edbc719b66763a571afbaf206cb6d3736d28255a46fc2fe20f902/websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-03-05T20:03:04Z, size = 181397, hashes = { sha256 = "ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054" } }, + { url = "https://files.pythonhosted.org/packages/2e/65/65f379525a2719e91d9d90c38fe8b8bc62bd3c702ac651b7278609b696c4/websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", upload-time = 2025-03-05T20:03:06Z, size = 181794, hashes = { sha256 = "4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee" } }, + { url = "https://files.pythonhosted.org/packages/d9/26/31ac2d08f8e9304d81a1a7ed2851c0300f636019a57cbaa91342015c72cc/websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", upload-time = 2025-03-05T20:03:08Z, size = 181194, hashes = { sha256 = "363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed" } }, + { url = "https://files.pythonhosted.org/packages/98/72/1090de20d6c91994cd4b357c3f75a4f25ee231b63e03adea89671cc12a3f/websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", upload-time = 2025-03-05T20:03:10Z, size = 181164, hashes = { sha256 = "2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880" } }, + { url = "https://files.pythonhosted.org/packages/2d/37/098f2e1c103ae8ed79b0e77f08d83b0ec0b241cf4b7f2f10edd0126472e1/websockets-15.0.1-cp39-cp39-win32.whl", upload-time = 2025-03-05T20:03:12Z, size = 176381, hashes = { sha256 = "3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411" } }, + { url = "https://files.pythonhosted.org/packages/75/8b/a32978a3ab42cebb2ebdd5b05df0696a09f4d436ce69def11893afa301f0/websockets-15.0.1-cp39-cp39-win_amd64.whl", upload-time = 2025-03-05T20:03:14Z, size = 176841, hashes = { sha256 = "b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4" } }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", upload-time = 2025-03-05T20:03:17Z, size = 173109, hashes = { sha256 = "0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3" } }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", upload-time = 2025-03-05T20:03:19Z, size = 173343, hashes = { sha256 = "1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1" } }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-03-05T20:03:21Z, size = 174599, hashes = { sha256 = "76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475" } }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-03-05T20:03:23Z, size = 174207, hashes = { sha256 = "f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9" } }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-03-05T20:03:25Z, size = 174155, hashes = { sha256 = "b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04" } }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", upload-time = 2025-03-05T20:03:27Z, size = 176884, hashes = { sha256 = "cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122" } }, + { url = "https://files.pythonhosted.org/packages/b7/48/4b67623bac4d79beb3a6bb27b803ba75c1bdedc06bd827e465803690a4b2/websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", upload-time = 2025-03-05T20:03:29Z, size = 173106, hashes = { sha256 = "7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940" } }, + { url = "https://files.pythonhosted.org/packages/ed/f0/adb07514a49fe5728192764e04295be78859e4a537ab8fcc518a3dbb3281/websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", upload-time = 2025-03-05T20:03:30Z, size = 173339, hashes = { sha256 = "47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e" } }, + { url = "https://files.pythonhosted.org/packages/87/28/bd23c6344b18fb43df40d0700f6d3fffcd7cef14a6995b4f976978b52e62/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2025-03-05T20:03:32Z, size = 174597, hashes = { sha256 = "67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9" } }, + { url = "https://files.pythonhosted.org/packages/6d/79/ca288495863d0f23a60f546f0905ae8f3ed467ad87f8b6aceb65f4c013e4/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2025-03-05T20:03:33Z, size = 174205, hashes = { sha256 = "d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b" } }, + { url = "https://files.pythonhosted.org/packages/04/e4/120ff3180b0872b1fe6637f6f995bcb009fb5c87d597c1fc21456f50c848/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2025-03-05T20:03:35Z, size = 174150, hashes = { sha256 = "4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f" } }, + { url = "https://files.pythonhosted.org/packages/cb/c3/30e2f9c539b8da8b1d76f64012f3b19253271a63413b2d3adb94b143407f/websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", upload-time = 2025-03-05T20:03:37Z, size = 176877, hashes = { sha256 = "21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123" } }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", upload-time = 2025-03-05T20:03:39Z, size = 169743, hashes = { sha256 = "f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f" } }, +] + +[[packages]] +name = "yamllint" +version = "1.37.1" +sdist = { url = "https://files.pythonhosted.org/packages/46/f2/cd8b7584a48ee83f0bc94f8a32fea38734cefcdc6f7324c4d3bfc699457b/yamllint-1.37.1.tar.gz", upload-time = 2025-05-04T08:25:54Z, size = 141613, hashes = { sha256 = "81f7c0c5559becc8049470d86046b36e96113637bcbe4753ecef06977c00245d" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/dd/b9/be7a4cfdf47e03785f657f94daea8123e838d817be76c684298305bd789f/yamllint-1.37.1-py3-none-any.whl", upload-time = 2025-05-04T08:25:52Z, size = 68813, hashes = { sha256 = "364f0d79e81409f591e323725e6a9f4504c8699ddf2d7263d8d2b539cd66a583" } }] From 4df590d839b0f6536ab911e39aa1d5d3e33ed687 Mon Sep 17 00:00:00 2001 From: "Charles P. Cross" <8572939+cpdata@users.noreply.github.com> Date: Thu, 16 Oct 2025 19:35:28 -0400 Subject: [PATCH 2/2] Replace REST stubs with FastAPI and wire gRPC CLI runtime --- CHANGELOG.md | 28 +++++++++ CLEANUP.md | 35 ++++++------ DUMMIES.md | 5 +- FINDINGS.md | 4 +- NEEDED_FOR_TESTING.md | 5 +- PLAN.md | 15 +++-- PROJECT.md | 11 ++-- README.md | 12 ++-- RECOMMENDATIONS.md | 7 ++- RESUME_NOTES.md | 46 ++++++--------- ROADMAP.md | 2 +- SETUP.md | 16 ++---- SOT.md | 2 +- TODO.md | 24 +++++--- docker-compose.yml | 27 +++++++++ docs/api.md | 17 ++++-- docs/operations.md | 6 +- docs/testing.md | 8 ++- meshmind/api/rest.py | 70 +++++------------------ meshmind/cli/__main__.py | 56 ++++++++++++++++++ meshmind/tasks/celery_app.py | 58 ++++++++----------- meshmind/tasks/scheduled.py | 13 +---- meshmind/tests/docker/full-stack.yml | 29 ++++++++++ meshmind/tests/test_cli_admin.py | 40 ++++++++++++- meshmind/tests/test_counts_smoke.py | 18 +++--- meshmind/tests/test_protos_packaging.py | 14 +++++ meshmind/tests/test_service_interfaces.py | 63 ++++++++++++-------- scripts/check_docs_sync.py | 2 +- scripts/check_protos.py | 8 +++ scripts/generate_protos.py | 10 ++++ 30 files changed, 414 insertions(+), 237 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f3831e..49890f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## [2025-10-16T16:35:00-04:00 (America/New_York)] +### Added +- Added a `serve-grpc` CLI subcommand (`meshmind/cli/__main__.py`) that instantiates + `MemoryService` via `create_graph_driver`, delegates to + `meshmind.api.grpc_server.serve_forever`, and is exercised by + `meshmind/tests/test_cli_admin.py` alongside the new docker-compose service + definitions (`docker-compose.yml`, `meshmind/tests/docker/full-stack.yml`). +- Recorded CLI runtime coverage and protobuf drift checks by extending + `meshmind/tests/test_cli_admin.py` and `meshmind/tests/test_protos_packaging.py`, + ensuring the new `serve-grpc` command and `scripts/check_protos.py` guard remain + functional. + +### Changed +- Removed the legacy REST stub and Celery fallbacks by requiring real FastAPI and + Celery imports (`meshmind/api/rest.py`, `meshmind/tasks/celery_app.py`, + `meshmind/tasks/scheduled.py`) and updated smoke tests to exercise the FastAPI + app via `fastapi.testclient.TestClient` (`meshmind/tests/test_service_interfaces.py`, + `meshmind/tests/test_counts_smoke.py`). +- Hardened protobuf utilities (`scripts/generate_protos.py`, `scripts/check_protos.py`) + to normalise relative imports so package builds remain import-safe. +- Updated provisioning assets and documentation (README.md, SETUP.md, + docs/api.md, docs/operations.md, docs/testing.md, PROJECT.md, PLAN.md, + RECOMMENDATIONS.md, FINDINGS.md, ROADMAP.md, DUMMIES.md, CLEANUP.md, + ENVIRONMENT_NEEDS.md, NEEDED_FOR_TESTING.md, SOT.md, TODO.md, RESUME_NOTES.md) + to describe the new gRPC workflow, retired shims, and compose stacks. +- Expanded the documentation guard mapping (`scripts/check_docs_sync.py`) so CLI + changes require updates to the API/operations guides. + ## [2025-10-16T12:06:05-04:00 (America/New_York)] ### Added - Introduced `meshmind/api/grpc_server.py` with `create_server`, `serve`, and `serve_forever` helpers so production deployments diff --git a/CLEANUP.md b/CLEANUP.md index a8e6953..88ca8dc 100644 --- a/CLEANUP.md +++ b/CLEANUP.md @@ -10,37 +10,34 @@ place, and the exact remediation steps required to bring the implementation in l - **Files**: `meshmind/_compat/pydantic.py`, modules importing from `meshmind._compat.pydantic`. - **Status**: Completed – the shim has been removed, `pydantic>=2.11` is a required dependency, and all models/tests now consume the official APIs directly. -### Retire FastAPI Stub +### Retire FastAPI Stub (Completed) - **Files**: `meshmind/api/rest.py`, `meshmind/api/service.py`, `docs/api.md`, `SETUP.md`. -- **Current State**: A lightweight FastAPI replacement exposes REST endpoints without requiring the framework. -- **Action**: - 1. Install `fastapi`, `uvicorn`, and related extras via the setup scripts. - 2. Replace the stub implementation with a true FastAPI app using pydantic request/response models. - 3. Update tests to spin up the FastAPI test client instead of the stub. - 4. Refresh documentation to reflect the production stack only. +- **Status**: Completed – the REST layer now requires FastAPI, tests rely on + `fastapi.testclient.TestClient`, and documentation reflects the production + stack. ### Replace gRPC Dataclass Shim (Completed) - **Files**: `meshmind/api/grpc.py`, `meshmind/protos/memory_service.proto`, `meshmind/tests/test_service_interfaces.py`, `docs/api.md`. - **Status**: Completed – protobuf definitions now live under `meshmind/protos`, generated modules back the Python stub, setup scripts install `grpcio`/`grpcio-tools`, and tests exercise the canonical schema. ### Promote gRPC Server Implementation -- **Files**: `meshmind/api/grpc.py`, future server entry point, integration tests. -- **Current State**: Tests rely on the in-process stub; there is no deployable server or channel bootstrap yet. +- **Files**: `meshmind/api/grpc.py`, `meshmind/api/grpc_server.py`, CLI entry points, + integration tests. +- **Current State**: An asyncio server helper and CLI (`meshmind serve-grpc`) now + run the service; Docker Compose provisions a gRPC container. Integration tests + against a live server remain outstanding. - **Action**: - 1. Implement a gRPC server (synchronous or `grpc.aio`) that binds the generated service to an actual channel. - 2. Add integration tests (possibly using `grpc.aio.insecure_channel`) that cover ingestion, search, and memory counts against the running server. - 3. Document provisioning steps (`SETUP.md`, `docs/api.md`) and add CLI helpers or docker-compose services if required. + 1. Add integration tests (possibly using `grpc.aio.insecure_channel`) that cover + ingestion, search, and memory counts against the running server. + 2. Publish generated client artifacts for downstream consumers once + infrastructure is available. ## Task Scheduling Workarounds -### Remove Celery Dummy App and Beat Fallback +### Remove Celery Dummy App and Beat Fallback (Completed) - **Files**: `meshmind/tasks/celery_app.py`, `meshmind/tasks/scheduled.py`. -- **Current State**: Custom placeholders allow imports without Celery and Celery Beat. -- **Action**: - 1. Make `celery` a required dependency for maintenance tasks. - 2. Refactor scheduling utilities to import real Celery constructs and fail fast when misconfigured. - 3. Add integration tests that execute Celery workers against Redis or RabbitMQ in docker-compose. - 4. Delete fallback classes once coverage exists. +- **Status**: Completed – Celery is a required dependency, the runtime imports the + real app/beat scheduler, and docker-compose stacks provision Redis for workers. ## Testing Fakes diff --git a/DUMMIES.md b/DUMMIES.md index 1a5e257..dbc03dd 100644 --- a/DUMMIES.md +++ b/DUMMIES.md @@ -6,10 +6,7 @@ recommended next step now that full dependencies can be installed. | Component | Location | Purpose | Current Usage | Recommended Action | | --- | --- | --- | --- | --- | -| FastAPI REST stub | `meshmind/api/rest.py` (`RestAPIStub`, `create_app`) | Exposes REST behaviour without the FastAPI dependency. | Tests use the stub when FastAPI is not installed; production should use FastAPI. | Keep temporarily for offline tests but plan to gate it behind an explicit test flag once FastAPI becomes mandatory. | | gRPC stub implementation | `meshmind/api/grpc.py` (`GrpcServiceStub` + generated protobuf helpers) | Provides an in-process service implementation backed by the canonical proto schema so tests can exercise the service layer without spinning up gRPC infrastructure. | Unit tests and docs rely on the stub while production traffic should flow through the new asyncio helpers in `meshmind.api.grpc_server`. | Keep the stub for tests; package the new server behind a CLI entry point and retire any ad-hoc wrappers once infrastructure is provisioned. | -| Celery dummy app | `meshmind/tasks/celery_app.py` (`_DummyCeleryApp`) | Allows module imports when Celery is missing. | Unit tests rely on the dummy to avoid Celery; production should use real Celery. | Retire dummy once Celery is a hard dependency; until then ensure tests explicitly exercise the real app when installed. | -| Celery beat fallback | `meshmind/tasks/scheduled.py` (`crontab` shim) | Supplies a no-op crontab when Celery beat is missing. | Prevents import errors in test environments without Celery. | Remove once Celery is required; otherwise guard usage with feature flags. | | Fake graph/storage drivers | `meshmind/testing/fakes.py` (`FakeMemgraphDriver`, `FakeRedisBroker`, `FakeEmbeddingEncoder`) | Provide offline stand-ins for Memgraph, Redis, and embedding models. | Pytest fixtures and documentation rely on these for isolation. | Keep as long as offline tests are desired; supplement with integration suites that use real services. | | Fake LLM client | `meshmind/testing/fakes.py` (`FakeLLMClient`) | Records per-request overrides and emits deterministic responses so tests exercise reranking without installing the OpenAI SDK. | Service/interface tests (`meshmind/tests/test_service_interfaces.py`, `test_client.py`) and the CLI fixtures inject this stub when `openai` is unavailable. | Keep for unit tests; add integration tests with real providers once keys and network access are provisioned. | | Dummy encoder fixture | `meshmind/tests/conftest.py` (`dummy_encoder`) and dependent tests | Supplies a lightweight embedding encoder for search tests. | Used across retrieval and service tests to avoid network calls. | Keep for unit tests; add integration coverage with real encoders once APIs are configured. | @@ -18,3 +15,5 @@ recommended next step now that full dependencies can be installed. ## Retired Items - **Pydantic compatibility shim (`meshmind/_compat/pydantic.py`)** – Removed now that the project installs `pydantic>=2.11` during setup. All models import directly from Pydantic and tests confirm no fallback is required. +- **FastAPI REST stub (`meshmind/api/rest.py`)** – Removed in favour of the concrete FastAPI application now that `fastapi` is a required dependency and tests exercise it via `TestClient`. +- **Celery dummy app and beat fallback (`meshmind/tasks/celery_app.py`, `meshmind/tasks/scheduled.py`)** – Retired because Celery is now installed by default; the real app and `crontab` integration are always imported. diff --git a/FINDINGS.md b/FINDINGS.md index 70e5c8c..00e86c0 100644 --- a/FINDINGS.md +++ b/FINDINGS.md @@ -2,7 +2,7 @@ ## General Observations - Core modules are now wired through the `MeshMind` client, including CRUD, triplet storage, and retrieval helpers. Graph-backed wrappers fetch namespace/entity-label filtered candidates from the configured driver automatically; remaining integration work focuses on server-side query optimisation and heuristic evaluation loops. -- Optional dependencies are largely guarded behind lazy imports or factory functions, improving portability. Environments still need to install tooling referenced by the Makefile and CI (ruff, pyright, typeguard, toml-sort, yamllint). `DUMMIES.md` now tracks remaining shims (FastAPI/gRPC/Celery) and documents retired items such as the former Pydantic fallback. +- Optional dependencies are largely guarded behind lazy imports or factory functions, improving portability. Environments still need to install tooling referenced by the Makefile and CI (ruff, pyright, typeguard, toml-sort, yamllint). `DUMMIES.md` now tracks remaining shims (gRPC stub, offline fakes) and documents retired items such as the former Pydantic fallback. - LLM usage is now centralized in `meshmind.llm_client` with per-operation defaults cascading from `LLM_*` environment variables and CLI overrides, reducing the risk of divergent configurations across pipelines and retrieval. REST/gRPC payloads now provide matching override dictionaries so services can experiment per request. - Timestamp helpers default to timezone-aware UTC, eliminating naive datetime outputs that previously leaked into maintenance pipelines and compatibility shims. - The gRPC interface is now defined by `meshmind/protos/memory_service.proto`; generated Python modules back the `GrpcServiceStub` @@ -22,7 +22,7 @@ embedding modules already encapsulate the SDK behind optional imports, easing the upcoming refactor to a dedicated wrapper. - Celery tasks initialize lazily, yet Redis/Memgraph services are still required at runtime. Docker Compose now provisions - Memgraph, Neo4j, and Redis, while targeted stacks under `meshmind/tests/docker/` support integration testing. `SETUP.md` + Memgraph, Neo4j, Redis, the gRPC server, and the Celery worker, while targeted stacks under `meshmind/tests/docker/` support integration testing. `SETUP.md` explains provisioning and teardown commands, and `scripts/benchmark_pagination.py` offers an offline way to measure driver throughput before integrating external services. ## Data Flow & Persistence diff --git a/NEEDED_FOR_TESTING.md b/NEEDED_FOR_TESTING.md index e42f9f1..94eb024 100644 --- a/NEEDED_FOR_TESTING.md +++ b/NEEDED_FOR_TESTING.md @@ -62,7 +62,10 @@ ## Local Configuration Steps - Ensure an embedding encoder is registered before extraction or hybrid search. The bootstrap utilities invoked by the CLI and `MeshMind` constructor handle this, but custom scripts must call `bootstrap_encoders()`. -- For REST/gRPC testing, instantiate the `RestAPIStub`/`GrpcServiceStub` with the in-memory driver to avoid external services. +- For REST/gRPC testing, instantiate the FastAPI app via `meshmind.api.rest.create_app` + and exercise it with `fastapi.testclient.TestClient` (requires the `httpx` + package); pair it with the `GrpcServiceStub` for lightweight gRPC coverage when + external services are unavailable. - Use `meshmind/testing` fakes (`FakeMemgraphDriver`, `FakeRedisBroker`, `FakeEmbeddingEncoder`, `FakeLLMClient`) in tests or demos to eliminate external infrastructure requirements. - Invoke `meshmind admin predicates` and `meshmind admin maintenance --max-attempts --base-delay --run ` during local runs to inspect predicate registries, telemetry, and tune maintenance retries without external services. - Use the benchmarking utilities in `scripts/` (`evaluate_importance.py`, `consolidation_benchmark.py`, `benchmark_pagination.py`) to validate heuristics and driver performance offline before connecting to live infrastructure. diff --git a/PLAN.md b/PLAN.md index 9010f9f..9d8ac5e 100644 --- a/PLAN.md +++ b/PLAN.md @@ -35,14 +35,17 @@ incrementally. 2. **Automation & CI** – Makefile provides lint/format/type/test/docs-guard targets and CI runs fmt-check, docs guard, and pytest. Protobuf drift now fails CI via `make protos-check`. Add caching and matrix builds when dependencies stabilize. -3. **Environment Provisioning** – Docker Compose now provisions Memgraph, Neo4j, and Redis (with targeted stacks for tests). - Track multi-backend examples, document the new `SETUP.md`, keep docs current, and distribute the new `run/install_setup.sh` - and `run/maintenance_setup.sh` automation scripts for environment bootstrap. +3. **Environment Provisioning** – Docker Compose now provisions Memgraph, Neo4j, + Redis, the gRPC server, and the Celery worker (with targeted stacks for tests). + Track multi-backend examples, document the new `SETUP.md`, keep docs current, + and distribute the new `run/install_setup.sh` and `run/maintenance_setup.sh` + automation scripts for environment bootstrap. ## Phase 5 – Strategic Enhancements (Planned) 1. **Graph-Backed Retrieval** – Extend the new driver-side filtering/pagination to full vector/lexical execution using backend-native indexes to avoid round-tripping candidate embeddings. 2. **Operational Observability** – Export telemetry to Prometheus/OpenTelemetry and surface dashboards/alerts. 3. **Celery Hardening** – Stress test consolidation/compression heuristics at scale and codify retry/backoff policies. -4. **Service Contracts** – Generated protobuf modules (`meshmind/protos/memory_service.proto`) back both the Python stub and the - new asyncio gRPC server helpers. Next: package a deployable entry point, publish generated clients, and add integration tests - once infrastructure is ready. +4. **Service Contracts** – Generated protobuf modules (`meshmind/protos/memory_service.proto`) + back both the Python stub and the asyncio gRPC server helpers. A dedicated CLI + entry point (`meshmind serve-grpc`) now launches the runtime. Next: publish + generated clients and add integration tests once infrastructure is ready. diff --git a/PROJECT.md b/PROJECT.md index ac69a3e..53f3db8 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -19,9 +19,10 @@ initialize lazily so import-time failures are avoided. - **Support code**: `meshmind.core` provides configuration, data models, embeddings, similarity math, and optional dependency guards around tokenization. -- **Service adapters**: `meshmind.api.rest` and `.grpc` expose REST/gRPC entry points. The gRPC surface now uses generated - protobuf messages (`meshmind/protos/memory_service.proto`) and runtime helpers in `meshmind.api.grpc_server` so tests, - scripts, and deployments share a canonical schema while retaining the in-process stub for fast feedback. +- **Service adapters**: `meshmind.api.rest` exposes a FastAPI application, while + `.grpc` ships generated protobuf stubs alongside asyncio server helpers and a CLI + (`meshmind serve-grpc`) so deployments share a canonical schema while retaining + the in-process stub for fast feedback. - **Observability**: `meshmind.core.observability` collects metrics, gauges, and structured log events across pipelines and scheduled tasks. - **Tooling**: The CLI ingest command (`meshmind ingest`), updated example script, Makefile automation, CI workflow, and Docker @@ -54,8 +55,8 @@ - Graph-backed retrieval still hydrates namespace/entity-label filtered candidates client-side; pushing ranking into the graph store is future work. - Predicate management remains internal to the bootstrap process; external administration APIs are still missing. - Metrics remain in-memory; external exporters (Prometheus/OpenTelemetry) are not wired up. -- gRPC deployments now rely on the asyncio server helpers; packaging an executable entry point and adding end-to-end integration - tests remain future work. +- gRPC deployments now rely on the asyncio server helpers; end-to-end integration + tests against a live CLI-managed server remain future work. ## External Services & Dependencies - **Graph backend**: Choose via `GRAPH_BACKEND`. In-memory and SQLite require no external services. Memgraph needs the `pymgclient` package (which exposes the `mgclient` module); diff --git a/README.md b/README.md index 868f949..162f85c 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ meshmind admin predicates --add RELATED_TO meshmind admin maintenance --max-attempts 5 --base-delay 2.5 --run consolidate meshmind admin graph --backend neo4j meshmind admin counts --namespace demo +meshmind serve-grpc --host 0.0.0.0 --port 50051 --backend memgraph ``` ## Maintenance Tasks @@ -190,8 +191,8 @@ Tasks instantiate the driver lazily, emit structured logs/metrics, and persist c to ensure code changes keep the wiki up to date. - **Examples** – `examples/extract_preprocess_store_example.py` demonstrates ingestion, triplet creation, and multiple retrieval strategies. -- **Dockerfile / docker-compose** – Container definition and orchestration files that provision Memgraph, Neo4j, Redis, and the - Celery worker stacks documented in `SETUP.md` and `meshmind/tests/docker/`. +- **Dockerfile / docker-compose** – Container definition and orchestration files that provision Memgraph, Neo4j, Redis, the + Celery worker, and the gRPC server documented in `SETUP.md` and `meshmind/tests/docker/`. - **Provisioning scripts** – `run/install_setup.sh` and `run/maintenance_setup.sh` validate that optional packages (`fastapi`, `neo4j`, `pymgclient`, `uvicorn`) are present and respect `MESH_SKIP_SYSTEM_PACKAGES=1` / `MESH_SKIP_PYTHON_SYNC=1` when you need a dry run without network access. @@ -206,7 +207,7 @@ Tasks instantiate the driver lazily, emit structured logs/metrics, and persist c Use `make benchmarks` to run all three scripts with synthetic defaults and capture JSON summaries under `build/benchmarks/`. ## Service Interfaces -- **REST** – `meshmind.api.rest.create_app` returns a FastAPI app (or lightweight stub) that exposes `/memories`, `/triplets`, +- **REST** – `meshmind.api.rest.create_app` returns a FastAPI app that exposes `/memories`, `/triplets`, `/search`, and `/memories/counts` endpoints. Search payloads accept: - `use_llm_rerank` to toggle LLM-based reranking, - `llm_models`, `llm_base_urls`, and `llm_api_key` dictionaries for per-request overrides, @@ -264,8 +265,9 @@ Tasks instantiate the driver lazily, emit structured logs/metrics, and persist c - MeshMind now depends on first-party Pydantic 2.x models; the legacy compatibility shim has been removed. - `meshmind/retrieval/bm25.py`, `meshmind/retrieval/fuzzy.py`, and `meshmind/core/similarity.py` include pure-Python fallbacks for scikit-learn, rapidfuzz, and numpy. - `meshmind/testing` exports fake Memgraph, Redis, and embedding drivers that power the pytest suite and examples without external infrastructure. -- `DUMMIES.md` lists every remaining stub (REST/gRPC service adapters, Celery fallbacks, compatibility layers) with guidance - on whether to remove or preserve them once external services are provisioned. +- `DUMMIES.md` lists every remaining stub (for example the gRPC in-process adapter + and offline test doubles) with guidance on whether to remove or preserve them + once external services are provisioned. ## Testing - Run `pytest` to execute the suite; tests rely on fixtures and fake drivers so they do not require external services or optional libraries. diff --git a/RECOMMENDATIONS.md b/RECOMMENDATIONS.md index 03d977b..262d9a5 100644 --- a/RECOMMENDATIONS.md +++ b/RECOMMENDATIONS.md @@ -5,15 +5,16 @@ - Maintain declared Python support at `>=3.11,<3.13` and monitor dependency releases before widening the range. - Harden the LLM-backed embedding adapter to consume SDK response objects directly and surface actionable errors for rate limits. - Expand automated smoke coverage (counts endpoints, maintenance retries) to the new SQLite/Neo4j drivers to ensure regressions are caught early. -- Continue using `DUMMIES.md` to track remaining shims (FastAPI/gRPC/Celery) and log retirements as dependencies graduate into the default bootstrap path. +- Continue using `DUMMIES.md` to track remaining shims (gRPC stub, fake drivers) and log retirements as dependencies graduate into the default bootstrap path. ## Restore and Extend Functionality - Extend the new server-side filtering and pagination work by pushing similarity ranking into Memgraph/Neo4j so vector scoring runs without loading namespaces in Python. - Validate consolidation heuristics at scale and tune the new exponential backoff settings (`MAINTENANCE_MAX_ATTEMPTS`, `MAINTENANCE_BASE_DELAY_SECONDS`) before enabling automated writes in production. - Leverage the new benchmarking scripts (`scripts/evaluate_importance.py`, `scripts/consolidation_benchmark.py`, `scripts/benchmark_pagination.py`) to validate heuristics and driver performance; schedule follow-up runs against production-sized datasets. - Introduce feedback loops for the importance heuristic (e.g., LLM-assisted ranking or analytics-driven weights) to tune thresholds over time once real-world telemetry is available. -- Build on the new `meshmind.api.grpc_server` helpers by packaging a deployable server entry point (CLI or module), wiring it - into Docker Compose, and publishing generated clients (Python + other languages) once infrastructure is available. +- Build on the new `meshmind.api.grpc_server` helpers and CLI entry point by + publishing generated clients (Python + other languages) and exercising + end-to-end smoke tests once infrastructure is available. - Exercise the new `llm_client` overrides via REST/gRPC integration smoke tests (once credentials are available) to confirm per-request models/endpoints behave consistently outside unit tests. - Expand predicate/registry management APIs beyond the CLI helper so services can manage vocabularies programmatically. - Plan for reintroducing full Pydantic models once packaging support is aligned with target Python versions. diff --git a/RESUME_NOTES.md b/RESUME_NOTES.md index 9aeaf11..33ebbbf 100644 --- a/RESUME_NOTES.md +++ b/RESUME_NOTES.md @@ -2,42 +2,28 @@ ## Current Context -- Branch: `integration-updates` (target PR branch: `integration`). -- Optional dependencies are bundled in the `.[dev,docs,testing]` extras, covering REST (`fastapi`, `uvicorn`), graph drivers - (`neo4j`, `pymgclient`, `redis`), Celery, LLM tooling, and developer linters. The provisioning scripts under `run/` - synchronize from `uv.lock` and verify these extras before attempting installs. -- Docker orchestration (root `docker-compose.yml` and the files in `meshmind/tests/docker/`) provisions Memgraph, Neo4j, Redis, - and optional Celery workers. Keep these references handy for live integration testing once container access is available. -- Documentation guard tooling requires that code edits touching modules with wiki pages also update the relevant files in - `docs/`. Planning artifacts (`PLAN.md`, `PROJECT.md`, `SOT.md`, `ROADMAP.md`, `PLANNING_THOUGHTS.md`) and the new `research/` - wiki are refreshed every iteration per agent instructions. +- Branch: `work` (rebased from `integration`; PR target remains `integration`). +- Optional dependencies ship with the `.[dev,docs,testing]` extras so local installs include FastAPI/Uvicorn, Celery/Redis, Neo4j/Memgraph drivers, LLM tooling, and developer linters. The provisioning scripts in `run/` validate these extras before syncing `uv.lock`. +- Docker orchestration (root `docker-compose.yml` and `meshmind/tests/docker/*.yml`) now provisions Memgraph, Neo4j, Redis, the Celery worker, and a gRPC server container driven by the new CLI command. +- The documentation guard enforces updates whenever code within mapped directories changes; planning artifacts (`PLAN.md`, `PROJECT.md`, `SOT.md`, `ROADMAP.md`, `PLANNING_THOUGHTS.md`, `research/`) remain synchronized per agent instructions. ## Latest Changes -- Added asyncio gRPC server helpers (`meshmind.api.grpc_server`) plus pytest coverage that ingests/searches over a live channel and exercises cancellation, completing the deployable-server TODO. -- Introduced protobuf maintenance scripts (`scripts/generate_protos.py`, `scripts/check_protos.py`) alongside Makefile/CI targets (`make protos`, `make protos-check`) and packaging tests so proto drift now fails fast. -- Updated README, SETUP, `docs/api.md`, `docs/operations.md`, `docs/testing.md`, `PROJECT.md`, `PLAN.md`, `RECOMMENDATIONS.md`, `FINDINGS.md`, `ISSUES.md`, `SOT.md`, `ROADMAP.md`, `DUMMIES.md`, and `ENVIRONMENT_NEEDS.md` with the new gRPC runtime guidance and planning follow-ups; `TODO.md` now tracks follow-on work (CLI entry point, docker-compose service, docs guard updates). +- Removed the temporary REST stub and Celery dummy app, requiring real FastAPI and Celery imports (`meshmind/api/rest.py`, `meshmind/tasks/celery_app.py`, `meshmind/tasks/scheduled.py`) and migrating smoke tests to `fastapi.testclient.TestClient`. +- Added a `serve-grpc` CLI subcommand that instantiates `MemoryService`, delegates to `meshmind.api.grpc_server.serve_forever`, and is invoked by new docker-compose services and CLI tests (`meshmind/tests/test_cli_admin.py`). +- Extended pytest coverage to execute `scripts/check_protos.py`, ensuring protobuf drift detection stays exercised alongside the runtime smoke tests. +- Updated documentation and planning collateral (README.md, SETUP.md, docs/api.md, docs/operations.md, docs/testing.md, PROJECT.md, PLAN.md, RECOMMENDATIONS.md, FINDINGS.md, ROADMAP.md, DUMMIES.md, CLEANUP.md, ENVIRONMENT_NEEDS.md, NEEDED_FOR_TESTING.md, SOT.md, TODO.md) to describe the retired shims and the new gRPC workflow. ## Environment State -- External graph services (Neo4j, Memgraph) and Redis are still unavailable inside this sandbox; integration tests depend on - fakes or SQLite until infrastructure is provisioned. -- Internet access is currently available; optional packages (`neo4j`, `pymgclient`, `fastapi`, `uvicorn`, etc.) are installed - via `uv pip`. Maintain this access so `uv.lock` can be regenerated and provisioning scripts remain effective. -- Timezone defaults across pipeline utilities now emit timezone-aware UTC timestamps; existing persisted data should be - reviewed once live backends are in play. +- External graph databases and Redis are still unavailable in this sandbox; tests rely on in-memory/SQLite drivers and fakes until remote services are provisioned. +- Internet access is currently enabled and optional packages have been installed via `uv pip`. Keep the network open so dependency locks and maintenance scripts remain functional. +- Changelog entries must continue using Eastern time with timezone codes; remember to update `CHANGELOG.md` after every batch of changes. ## Next Session Starting Points -1. Work through the remaining `TODO.md` priority items (Neo4j validation, backend-native vector search, live benchmarking, CLI gRPC entry point, docker-compose service). Add follow-up tasks as new discoveries surface. -2. Validate Neo4j connectivity end-to-end once a reachable instance is available, using `meshmind admin graph --backend neo4j` and the docker-compose stack. -3. Run the benchmarking scripts against production-sized or synthetic datasets hosted externally to calibrate maintenance retry defaults and pagination guidance, then document recommended values in `README.md` / `ENVIRONMENT_NEEDS.md`. -4. Implement the CLI gRPC entry point (`meshmind serve-grpc`), update docker-compose stacks to launch it, and extend docs/testing guard mappings accordingly. -5. Push vector similarity into Memgraph/Neo4j or document fallback limitations once native index strategies are available; continue updating `docs/api.md`/`docs/retrieval.md` with findings. - -## Helpful References - -- `docs/overview.md` for the module map and terminology. -- `docs/configuration.md` and `docs/operations.md` for environment variables and provisioning details. -- `SETUP.md`, `ENVIRONMENT_NEEDS.md`, and `NEEDED_FOR_TESTING.md` for onboarding and infrastructure expectations. -- `TODO.md` and `ISSUES.md` for the prioritized backlog and outstanding blockers. +1. Address the `TODO.md` priority items that require infrastructure: Neo4j connectivity validation, backend-native vector similarity, large-scale benchmarking, and integration tests for Celery/gRPC once services are reachable. +2. Coordinate with the human operator to provision Neo4j/Memgraph/Redis instances and approve installing optional dependencies across CI and developer machines. +3. Prepare integration smoke tests that exercise `meshmind serve-grpc` via grpcurl and verify the documented REST/grpcurl snippets once remote endpoints exist. +4. Plan the publication of protobuf-generated client artifacts and documentation updates (release notes, gRPC usage examples) after integration tests succeed. +5. Continue tracking shim retirements in `DUMMIES.md` and follow the cleanup plan in `CLEANUP.md` so remaining fakes can be removed when infrastructure allows. diff --git a/ROADMAP.md b/ROADMAP.md index e2fa7d9..2cf0bee 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -15,7 +15,7 @@ ## Mid-Term (2–6 Weeks) - Run load tests against SQLite and hosted graph backends to tune pagination defaults, consolidation heuristics, and token compression strategies. - Implement backend-native vector similarity queries and schema indexes so embeddings never leave the database during scoring. -- Finalise the gRPC surface by building on the new asyncio server helpers—package a deployable CLI/entry point, wire it into Docker Compose, and publish generated clients (Python + additional languages) so external agents can integrate without the in-process stub. +- Finalise the gRPC surface by building on the new asyncio server helpers—exercise the `meshmind serve-grpc` CLI entry point within Docker Compose, publish generated clients (Python + additional languages), and add integration smoke tests so external agents can integrate without the in-process stub. - Instrument observability exports (Prometheus/OpenTelemetry) and wire dashboards/alerts for ingestion latency, queue depth, and error rates. - Replace compatibility shims with official Pydantic/FastAPI packages once dependency constraints are lifted, and backfill validation coverage. diff --git a/SETUP.md b/SETUP.md index 201767a..ee78505 100644 --- a/SETUP.md +++ b/SETUP.md @@ -48,8 +48,9 @@ execution and automated testing. ## 3. Provision external services -The repository ships with a root `docker-compose.yml` that starts Redis, Memgraph, and -Neo4j with sane defaults. Run the stack in the background: +The repository ships with a root `docker-compose.yml` that starts Redis, Memgraph, +Neo4j, the gRPC API server, and the Celery worker with sane defaults. Run the stack in +the background: ```bash docker compose up -d @@ -69,7 +70,7 @@ For integration scenarios or CI jobs, use the compose files in | `memgraph.yml` | Runs only Memgraph with local port mapping. | | `neo4j.yml` | Runs only Neo4j with APOC enabled. | | `redis.yml` | Runs only Redis with persistence. | -| `full-stack.yml` | Spins up all services plus an optional Celery worker. | +| `full-stack.yml` | Spins up all services, the Celery worker, and the gRPC server. | Example (Memgraph only): @@ -130,13 +131,8 @@ You should see passing pytest output and successful graph connectivity checks. R To validate the gRPC surface locally: ```bash -python -c "from meshmind.api.grpc_server import serve_forever;\nfrom meshmind.api.memory_manager import MemoryManager;\nfrom meshmind.api.service import MemoryService;\nfrom meshmind.db.in_memory_driver import InMemoryGraphDriver;\nserve_forever(MemoryService(MemoryManager(InMemoryGraphDriver())), host='0.0.0.0', port=50051)" -``` - -In a second terminal, run: - -```bash +meshmind serve-grpc --host 0.0.0.0 --port 50051 --backend memgraph grpcurl -plaintext -d '{"namespace":"demo"}' localhost:50051 meshmind.api.MemoryService/MemoryCounts ``` -Stop the server with `Ctrl+C` when finished. +Stop the server with `Ctrl+C` when finished (the CLI handles graceful shutdown). diff --git a/SOT.md b/SOT.md index 5d252ec..f3287fb 100644 --- a/SOT.md +++ b/SOT.md @@ -97,7 +97,7 @@ Supporting assets: ## Service Layers (`meshmind/api`) - `memory_manager.py`: CRUD façade over the active graph driver that forwards namespace/entity-label filters, pagination hints, search strings, and exposes aggregate counts alongside triplet listings. - `service.py`: Pydantic payloads and orchestration helpers shared by REST/gRPC surfaces. `MemoryService.search` leans on driver-side filtering before ranking, handles per-request LLM overrides (`use_llm_rerank`, `llm_models`, `llm_base_urls`, `llm_api_key`, `rerank_model`), and exposes `memory_counts` for CLI/HTTP usage. -- `rest.py`: `create_app` returns a FastAPI application when available or a `RestAPIStub` for tests. Routes support pagination parameters and include `/memories/counts` for namespace/label summaries. +- `rest.py`: `create_app` returns a FastAPI application exposing ingestion, retrieval, and reporting routes. Routes support pagination parameters and include `/memories/counts` for namespace/label summaries. - `grpc.py`: `GrpcServiceStub` backed by generated protobuf messages (`meshmind/protos/memory_service.proto`) to keep the Python stub aligned with the production RPC schema. - `grpc_server.py`: Async server helpers (`create_server`, `serve`, `serve_forever`) that expose the canonical RPC interface diff --git a/TODO.md b/TODO.md index b1679ce..12490b9 100644 --- a/TODO.md +++ b/TODO.md @@ -73,6 +73,12 @@ - [x] Add packaging tests to guarantee `meshmind/protos/memory_service.proto` ships with the distribution and exposes the expected service definition. - [x] Document runtime and operational guidance for the gRPC server across README, SETUP, `docs/api.md`, and `docs/operations.md`. - [x] Add Makefile and CI targets (`make protos`, `make protos-check`) plus scripts to regenerate/verify protobuf bindings, failing CI when drift occurs. +- [x] Replace the REST stub with the concrete FastAPI application and migrate smoke tests to `fastapi.testclient.TestClient`. +- [x] Remove Celery dummy fallbacks by requiring the real app/beat imports and keeping docker-compose stacks in sync. +- [x] Add a `serve-grpc` CLI subcommand and verify it delegates to the runtime helpers. +- [x] Teach docker-compose stacks (root and `meshmind/tests/docker/full-stack.yml`) to launch the gRPC service via the new CLI entry point. +- [x] Add pytest coverage for `scripts/check_protos.py` so protobuf drift detection stays exercised. +- [x] Expand the documentation guard mapping to require API/operations updates when CLI modules change. ## Priority Tasks @@ -82,14 +88,16 @@ - [ ] Run `scripts/benchmark_pagination.py` against live Memgraph/Neo4j instances to tune default pagination limits and capture guidance in `docs/retrieval.md`. - [ ] Implement integration tests exercising `meshmind admin maintenance --max-attempts/--base-delay` with a real Celery worker and Redis once infrastructure is available. - [ ] Validate the documented curl/grpcurl snippets against deployed REST/gRPC services (with auth) once staging environments are reachable. -- [ ] Add a CLI entry point (e.g., `meshmind serve-grpc`) that constructs a `MemoryService` from settings and delegates to `meshmind.api.grpc_server.serve_forever`, with pytest coverage exercising the command in-process. -- [ ] Teach `docker-compose.yml` (and the targeted stacks) how to launch the gRPC server container once the CLI entry point exists, documenting the new service in `docs/operations.md` and `SETUP.md`. -- [ ] Add pytest coverage that executes `python scripts/check_protos.py` to ensure the verification script succeeds against the current tree. -- [ ] Extend `docs/testing.md` and the docs guard mapping so gRPC runtime changes require updates to the new CLI/server documentation once the entry point lands. +- [ ] Add integration tests that spin up `meshmind serve-grpc` and exercise ingestion/search via grpcurl to complement the unit-level coverage (blocked until network-accessible infrastructure is ready). +- [ ] Publish protobuf-generated client artifacts (Python wheel or language-neutral bundles) so external services can consume the API once infrastructure is available. +- [ ] Provision Neo4j, Memgraph, and Redis instances accessible from the development environment to unblock live integration tests (requires infrastructure support). +- [ ] Approve and install optional dependencies (`neo4j`, `pymgclient`, `redis`, `celery`, `tiktoken`, `sentence-transformers`) across CI and developer machines to exercise full workflows. +- [ ] Source or generate large synthetic datasets for consolidation and retrieval benchmarking to validate heuristics under load. +- [ ] Define and ratify a policy for reintroducing Pydantic models (version targets, rollout timeline) so compatibility shims remain unnecessary going forward. +- [ ] Document the retired REST/Celery shims in release notes and communicate migration steps to downstream integrators. +- [ ] Capture gRPC CLI usage examples (including docker-compose orchestration) in `docs/api.md` and `docs/operations.md` once integration smoke tests complete. ## Recommended Waiting for Approval Tasks -- [ ] Provision Neo4j, Memgraph, and Redis instances accessible from the development environment to unblock live integration tests (requires infrastructure approval). -- [ ] Approve installation of optional dependencies (`neo4j`, `pymgclient`, `redis`, `celery`, `tiktoken`, `sentence-transformers`) across CI and developer machines to exercise full workflows. -- [ ] Source or generate large synthetic datasets for consolidation and retrieval benchmarking to validate heuristics under load. -- [ ] Define a policy for reintroducing Pydantic models (version targets, rollout timeline) so compatibility shims can be retired once approved. +- [ ] Identify candidate observability exporters (Prometheus/OpenTelemetry) and draft rollout steps for external telemetry sinks. +- [ ] Explore UI concepts for inspecting memories/triplets once the API hardening tasks land. diff --git a/docker-compose.yml b/docker-compose.yml index cdf9fca..4aa6cc8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,6 +62,33 @@ services: networks: - meshmind + grpc: + build: . + container_name: meshmind-grpc + command: + - meshmind + - serve-grpc + - --host + - 0.0.0.0 + - --port + - "50051" + - --backend + - memgraph + environment: + GRAPH_BACKEND: memgraph + MEMGRAPH_URI: bolt://memgraph:7687 + REDIS_URL: redis://redis:6379/0 + PYTHONUNBUFFERED: "1" + depends_on: + memgraph: + condition: service_healthy + redis: + condition: service_healthy + ports: + - "50051:50051" + networks: + - meshmind + networks: meshmind: name: meshmind diff --git a/docs/api.md b/docs/api.md index e2e6485..abf2a3f 100644 --- a/docs/api.md +++ b/docs/api.md @@ -13,7 +13,8 @@ MeshMind exposes multiple integration points for ingestion and retrieval workflo ## REST API (`meshmind.api.rest`) -- `create_app` dynamically returns a FastAPI application if FastAPI is installed, otherwise a lightweight stub. +- `create_app` returns a FastAPI application exposing ingestion, retrieval, and + reporting routes. - Routes: - `POST /memories`: ingest a batch of memories. - `POST /triplets`: ingest relationships. @@ -54,7 +55,6 @@ MeshMind exposes multiple integration points for ingestion and retrieval workflo "namespace": "demo" } ``` -- The `RestAPIStub` mirrors these routes for tests without requiring FastAPI. - Example `curl` invocations against a local FastAPI server: ```bash curl -s -X POST http://localhost:8000/search \ @@ -82,10 +82,15 @@ MeshMind exposes multiple integration points for ingestion and retrieval workflo ## CLI (`meshmind/cli`) -- `meshmind.cli.__main__` bootstraps default encoders, validates configuration, and exposes ingestion commands. -- `meshmind.cli.admin` contains administrative tasks for registry inspection, backend connectivity checks, memory counts, - and maintenance triggers. Use `meshmind admin maintenance --max-attempts --base-delay --run ` to - override retry/backoff settings per run. +- `meshmind.cli.__main__` bootstraps default encoders, validates configuration, + and exposes ingestion commands. +- `meshmind.cli.admin` contains administrative tasks for registry inspection, + backend connectivity checks, memory counts, and maintenance triggers. Use + `meshmind admin maintenance --max-attempts --base-delay --run ` + to override retry/backoff settings per run. +- `meshmind serve-grpc --host 0.0.0.0 --port 50051` launches the asyncio gRPC + server defined in `meshmind.api.grpc_server` using the configured graph backend + and LLM settings. ## Client (`meshmind/client.py`) diff --git a/docs/operations.md b/docs/operations.md index ae95762..24a0d89 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -27,6 +27,8 @@ This guide covers operational tasks for MeshMind deployments. the check script automatically so outdated bindings fail fast. - Construct a `MemoryService` (for example, using the driver factory) and call `meshmind.api.grpc_server.serve_forever(service, host="0.0.0.0", port=50051)` to expose the RPC interface. +- The CLI provides `meshmind serve-grpc --host 0.0.0.0 --port 50051 --backend memgraph` + to launch the server with configuration derived from environment variables. - To embed the server inside a larger application, call `meshmind.api.grpc_server.create_server(...)`, start the returned `grpc.aio.Server`, and integrate its lifecycle with your event loop. - `grpcurl -plaintext -d '{"namespace":"demo"}' localhost:50051 meshmind.api.MemoryService/MemoryCounts` queries the @@ -75,4 +77,6 @@ This guide covers operational tasks for MeshMind deployments. - Provision graph databases externally (Docker, managed service) and expose Bolt endpoints reachable from the runtime. - Ensure optional dependencies (`neo4j`, `pymgclient`, `redis`, `celery`, `fastapi`, `uvicorn`, `tiktoken`) are installed where required (or install `.[dev,docs,testing]`). - Configure logging/metrics sinks to capture telemetry emitted by pipeline stages. -- Use `docker-compose.yml` as a reference for local orchestration (Memgraph, Neo4j, Redis) and the targeted stacks in `meshmind/tests/docker/` for integration testing. +- Use `docker-compose.yml` as a reference for local orchestration (Memgraph, Neo4j, + Redis, gRPC server, Celery worker) and the targeted stacks in + `meshmind/tests/docker/` for integration testing. diff --git a/docs/testing.md b/docs/testing.md index d19a486..33e6ab6 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -10,10 +10,14 @@ backends. - `meshmind/tests/test_db_*`: ensure graph drivers implement CRUD semantics (with fakes for optional dependencies). - `meshmind/tests/test_retrieval.py` and `test_graph_retrieval.py`: exercise hybrid, textual, vector, and graph-based searches. -- `meshmind/tests/test_service_interfaces.py`: cover REST and gRPC stubs, including entity-label filtering, pagination hints, and memory count routes with LLM override payloads. +- `meshmind/tests/test_service_interfaces.py`: cover the FastAPI REST app and + gRPC stub, including entity-label filtering, pagination hints, and memory count + routes with LLM override payloads. - `meshmind/tests/test_grpc_runtime.py`: spin up the asyncio gRPC server helpers to confirm ingestion/search round-trips work over the network and that cancellation shuts servers down cleanly. - `meshmind/tests/test_setup_scripts.py`: run the provisioning scripts in validation mode to ensure optional dependencies are declared in `pyproject.toml`, that `uv` is bootstrapped, and that skip flags behave as expected. -- `meshmind/tests/test_cli_admin.py`: verify administrative CLI commands use the correct driver factory, settings, and counts reporting. +- `meshmind/tests/test_cli_admin.py`: verify administrative CLI commands use the + correct driver factory, settings, and counts reporting, and confirm the + `meshmind serve-grpc` command delegates to the runtime helpers. - `meshmind/tests/test_counts_smoke.py`: exercise the REST `/memories/counts` endpoint and `meshmind admin counts` command against the in-memory driver as smoke coverage. - `meshmind/tests/test_tasks_scheduled.py`: assert maintenance consolidation retries handle transient conflicts via configurable backoff. - `meshmind/tests/test_docs_guard.py`: ensure the documentation guard script enforces wiki updates when code modules change. diff --git a/meshmind/api/rest.py b/meshmind/api/rest.py index 4a8fb23..495c678 100644 --- a/meshmind/api/rest.py +++ b/meshmind/api/rest.py @@ -3,65 +3,13 @@ from typing import Any, Dict, Iterable, List -from meshmind.api.service import MemoryPayload, MemoryService, SearchPayload, TripletPayload - - -class RestAPIStub: - """Fallback handler that emulates REST routes without FastAPI.""" - - def __init__(self, service: MemoryService) -> None: - self.service = service +from fastapi import FastAPI, HTTPException, Query - def dispatch(self, method: str, path: str, payload: Dict[str, Any] | None = None) -> Dict[str, Any]: - method = method.upper() - payload = payload or {} - if method == "POST" and path == "/memories": - memories = [MemoryPayload(**item) for item in payload.get("memories", [])] - uuids = self.service.ingest_memories(memories) - return {"uuids": uuids} - if method == "POST" and path == "/triplets": - triplets = [TripletPayload(**item) for item in payload.get("triplets", [])] - count = self.service.ingest_triplets(triplets) - return {"stored": count} - if method == "POST" and path == "/search": - request = SearchPayload(**payload) - results = self.service.search(request) - return {"results": [mem.model_dump(exclude_none=True) for mem in results]} - if method == "GET" and path == "/memories": - namespace = payload.get("namespace") - entity_labels = payload.get("entity_labels") - offset = int(payload.get("offset", 0)) - limit_value = payload.get("limit") - limit = int(limit_value) if limit_value is not None else None - query = payload.get("query") - use_search = payload.get("use_search") - memories = self.service.list_memories( - namespace, - entity_labels, - offset=offset, - limit=limit, - query=query, - use_search=use_search, - ) - return {"memories": [mem.model_dump(exclude_none=True) for mem in memories]} - if method == "GET" and path == "/memories/counts": - namespace = payload.get("namespace") - counts = self.service.memory_counts(namespace) - return {"counts": counts} - if method == "GET" and path == "/triplets": - namespace = payload.get("namespace") - triplets = self.service.list_triplets(namespace) - return {"triplets": [triplet.model_dump(exclude_none=True) for triplet in triplets]} - raise ValueError(f"Unsupported route {method} {path}") +from meshmind.api.service import MemoryPayload, MemoryService, SearchPayload, TripletPayload def create_app(service: MemoryService) -> Any: - """Create a FastAPI application if FastAPI is installed, otherwise return a stub.""" - - try: # pragma: no cover - optional dependency path - from fastapi import FastAPI, HTTPException - except ImportError: # pragma: no cover - executed in tests without fastapi - return RestAPIStub(service) + """Create a FastAPI application exposing MeshMind service routes.""" app = FastAPI(title="MeshMind API") @@ -95,15 +43,23 @@ def search(payload: Dict[str, Any]): @app.get("/memories") def list_memories( namespace: str | None = None, - entity_labels: List[str] | None = None, + entity_labels: List[str] | None = Query(default=None), offset: int = 0, limit: int | None = None, query: str | None = None, use_search: bool | None = None, ): + labels: List[str] | None + if entity_labels is None: + labels = None + elif isinstance(entity_labels, str): # pragma: no cover - defensive guard + labels = [entity_labels] + else: + labels = list(entity_labels) + memories = service.list_memories( namespace, - entity_labels, + labels, offset=offset, limit=limit, query=query, diff --git a/meshmind/cli/__main__.py b/meshmind/cli/__main__.py index 0b39cca..1b8ea1c 100644 --- a/meshmind/cli/__main__.py +++ b/meshmind/cli/__main__.py @@ -5,10 +5,39 @@ import argparse import sys +from meshmind.api.grpc_server import serve_forever +from meshmind.api.memory_manager import MemoryManager +from meshmind.api.service import MemoryService from meshmind.cli.admin import register_admin_subcommands from meshmind.cli.ingest import ingest_command from meshmind.core.bootstrap import bootstrap_encoders, bootstrap_entities from meshmind.core.config import settings +from meshmind.db.factory import create_graph_driver +from meshmind.llm_client import build_default_llm_client + + +def serve_grpc_command(args: argparse.Namespace) -> None: + """Start the MeshMind gRPC server using the configured settings.""" + + backend = args.backend or settings.GRAPH_BACKEND + driver = create_graph_driver(backend=backend) + manager = MemoryManager(driver) + service = MemoryService( + manager, + llm_client_factory=lambda: build_default_llm_client(settings), + ) + + try: + serve_forever( + service, + host=args.host, + port=args.port, + shutdown_grace=args.shutdown_grace, + ) + finally: + closer = getattr(driver, "close", None) + if callable(closer): + closer() def main(): @@ -74,6 +103,33 @@ def main(): register_admin_subcommands(subparsers) + serve_grpc_parser = subparsers.add_parser( + "serve-grpc", help="Run the MeshMind gRPC service" + ) + serve_grpc_parser.add_argument( + "--host", + default="0.0.0.0", + help="Interface to bind the gRPC server (default: 0.0.0.0)", + ) + serve_grpc_parser.add_argument( + "--port", + type=int, + default=50051, + help="Port to bind the gRPC server (default: 50051)", + ) + serve_grpc_parser.add_argument( + "--backend", + default=None, + help="Graph backend to use (overrides GRAPH_BACKEND when provided)", + ) + serve_grpc_parser.add_argument( + "--shutdown-grace", + type=float, + default=5.0, + help="Seconds to wait for in-flight RPCs during shutdown", + ) + serve_grpc_parser.set_defaults(func=serve_grpc_command) + args = parser.parse_args() # Ensure default encoders and entities are registered before executing commands diff --git a/meshmind/tasks/celery_app.py b/meshmind/tasks/celery_app.py index 46fe7f9..8eb5eb8 100644 --- a/meshmind/tasks/celery_app.py +++ b/meshmind/tasks/celery_app.py @@ -1,39 +1,31 @@ -""" -Celery application setup for MeshMind maintenance tasks. -If Celery is not installed, provides a dummy app for imports. -""" -try: - from celery import Celery -except ImportError: - Celery = None # type: ignore - -class _DummyConf: - pass - -class _DummyCeleryApp: - def __init__(self): - self.conf = _DummyConf() - - def task(self, name=None): - def decorator(fn): - return fn - return decorator - -if Celery: - from meshmind.core.config import settings - - # Initialize Celery app with Redis broker +"""Celery application setup for MeshMind maintenance tasks.""" + +from __future__ import annotations + +from celery import Celery + +from meshmind.core.config import settings + + +def _create_celery_app() -> Celery: + """Initialise the Celery application bound to the configured Redis broker.""" + app = Celery( - 'meshmind', + "meshmind", broker=settings.REDIS_URL, backend=settings.REDIS_URL, ) - # Celery configuration app.conf.result_backend = settings.REDIS_URL - app.conf.task_serializer = 'json' - app.conf.result_serializer = 'json' - app.conf.accept_content = ['json'] - app.conf.timezone = 'UTC' + app.conf.task_serializer = "json" + app.conf.result_serializer = "json" + app.conf.accept_content = ["json"] + app.conf.timezone = "UTC" app.conf.enable_utc = True -else: - app = _DummyCeleryApp() + app.conf.broker_connection_retry_on_startup = True + return app + + +app = _create_celery_app() + + +__all__ = ["app", "_create_celery_app"] diff --git a/meshmind/tasks/scheduled.py b/meshmind/tasks/scheduled.py index 77c145b..f9775d3 100644 --- a/meshmind/tasks/scheduled.py +++ b/meshmind/tasks/scheduled.py @@ -4,14 +4,7 @@ import time from typing import Callable -try: - from celery.schedules import crontab - _CELERY_BEAT = True -except ImportError: - # Celery not installed; define dummy crontab - _CELERY_BEAT = False - def crontab(*args, **kwargs): # type: ignore - return None +from celery.schedules import crontab from meshmind.api.memory_manager import MemoryManager from meshmind.core.config import settings from meshmind.core.observability import log_event, telemetry @@ -87,8 +80,8 @@ def _get_manager() -> MemoryManager | None: _MANAGER = MemoryManager(driver) return _MANAGER -# Define periodic task schedule if Celery is available -if _CELERY_BEAT and hasattr(app, 'conf'): +# Define periodic task schedule +if hasattr(app, "conf"): app.conf.beat_schedule = { 'expire-memories-every-day': { 'task': 'meshmind.tasks.scheduled.expire_task', diff --git a/meshmind/tests/docker/full-stack.yml b/meshmind/tests/docker/full-stack.yml index 2b396b2..6e830fd 100644 --- a/meshmind/tests/docker/full-stack.yml +++ b/meshmind/tests/docker/full-stack.yml @@ -44,6 +44,35 @@ services: - ../../..:/app working_dir: /app + grpc: + build: + context: ../../.. + container_name: meshmind-int-grpc + command: + - meshmind + - serve-grpc + - --host + - 0.0.0.0 + - --port + - "50051" + - --backend + - memgraph + environment: + GRAPH_BACKEND: memgraph + MEMGRAPH_URI: bolt://memgraph:7687 + REDIS_URL: redis://redis:6379/0 + PYTHONUNBUFFERED: "1" + depends_on: + memgraph: + condition: service_healthy + redis: + condition: service_healthy + ports: + - "25051:50051" + volumes: + - ../../..:/app + working_dir: /app + networks: default: name: meshmind-integration diff --git a/meshmind/tests/test_cli_admin.py b/meshmind/tests/test_cli_admin.py index 592950c..0a777fd 100644 --- a/meshmind/tests/test_cli_admin.py +++ b/meshmind/tests/test_cli_admin.py @@ -1,10 +1,10 @@ from argparse import Namespace -from argparse import Namespace from io import StringIO import pytest from meshmind.cli import admin +from meshmind.cli.__main__ import serve_grpc_command from meshmind.core.observability import telemetry from meshmind.models.registry import PredicateRegistry @@ -105,3 +105,41 @@ def close(self): assert status == 0 assert "\"Note\": 3" in stream.getvalue() + + +def test_serve_grpc_command_invokes_runtime(monkeypatch): + drivers = [] + calls = [] + + class DummyDriver: + def __init__(self) -> None: + self.closed = False + + def close(self) -> None: + self.closed = True + + def fake_create_graph_driver(backend=None): # noqa: ANN001 - signature match + driver = DummyDriver() + drivers.append((backend, driver)) + return driver + + def fake_serve_forever(service, host, port, shutdown_grace, **kwargs): # noqa: ANN001 + calls.append({ + "service": service, + "host": host, + "port": port, + "shutdown_grace": shutdown_grace, + }) + + monkeypatch.setattr("meshmind.cli.__main__.create_graph_driver", fake_create_graph_driver) + monkeypatch.setattr("meshmind.cli.__main__.serve_forever", fake_serve_forever) + + args = Namespace(host="127.0.0.1", port=50052, backend="sqlite", shutdown_grace=1.5) + + serve_grpc_command(args) + + assert calls and calls[0]["host"] == "127.0.0.1" + assert calls[0]["port"] == 50052 + assert abs(calls[0]["shutdown_grace"] - 1.5) < 1e-6 + assert drivers and drivers[0][0] == "sqlite" + assert drivers[0][1].closed diff --git a/meshmind/tests/test_counts_smoke.py b/meshmind/tests/test_counts_smoke.py index 0dae026..f1ad22a 100644 --- a/meshmind/tests/test_counts_smoke.py +++ b/meshmind/tests/test_counts_smoke.py @@ -1,10 +1,12 @@ import argparse +import argparse import json import pytest +from fastapi.testclient import TestClient from meshmind.api.memory_manager import MemoryManager -from meshmind.api.rest import RestAPIStub +from meshmind.api.rest import create_app from meshmind.api.service import MemoryService from meshmind.cli import admin from meshmind.core.types import Memory @@ -29,14 +31,16 @@ def populated_service(): def test_rest_counts_endpoint_returns_totals(populated_service): - driver, service = populated_service - api = RestAPIStub(service) + _, service = populated_service + client = TestClient(create_app(service)) - response = api.dispatch("GET", "/memories/counts", {"namespace": "docs"}) + response = client.get("/memories/counts", params={"namespace": "docs"}) - assert "counts" in response - assert response["counts"]["docs"]["Note"] == 2 - assert "support" not in response["counts"] + assert response.status_code == 200 + payload = response.json() + assert "counts" in payload + assert payload["counts"]["docs"]["Note"] == 2 + assert "support" not in payload["counts"] def test_cli_admin_counts_reports_json(populated_service, monkeypatch, capsys): diff --git a/meshmind/tests/test_protos_packaging.py b/meshmind/tests/test_protos_packaging.py index 7d6ea99..b8bc426 100644 --- a/meshmind/tests/test_protos_packaging.py +++ b/meshmind/tests/test_protos_packaging.py @@ -1,6 +1,8 @@ """Ensure protobuf assets ship with the package.""" from __future__ import annotations +import subprocess +import sys from pathlib import Path from meshmind.protos import data_path @@ -11,3 +13,15 @@ def test_proto_files_packaged() -> None: assert proto_path.exists() text = proto_path.read_text(encoding="utf-8") assert "service MeshMindService" in text + + +def test_check_protos_script_runs_successfully() -> None: + repo_root = Path(__file__).resolve().parents[2] + result = subprocess.run( + [sys.executable, "scripts/check_protos.py"], + cwd=repo_root, + capture_output=True, + text=True, + check=True, + ) + assert "Protobuf bindings are up to date." in result.stderr diff --git a/meshmind/tests/test_service_interfaces.py b/meshmind/tests/test_service_interfaces.py index 628a647..2a22949 100644 --- a/meshmind/tests/test_service_interfaces.py +++ b/meshmind/tests/test_service_interfaces.py @@ -1,7 +1,9 @@ from uuid import UUID +from fastapi.testclient import TestClient + from meshmind.api.grpc import GrpcServiceStub, memory_to_proto, triplet_to_proto -from meshmind.api.rest import RestAPIStub +from meshmind.api.rest import create_app from meshmind.api.service import MemoryPayload, SearchPayload, TripletPayload from meshmind.core.types import Memory from meshmind.protos import memory_service_pb2 as pb2 @@ -47,27 +49,34 @@ def fake_list(namespace=None, entity_labels=None, **kwargs): # noqa: ANN001 assert captured["kwargs"]["limit"] is not None -def test_rest_stub_routes(memory_service, dummy_encoder): - app = RestAPIStub(memory_service) - response = app.dispatch( - "POST", +def test_rest_routes(memory_service, dummy_encoder): + client = TestClient(create_app(memory_service)) + + response = client.post( "/memories", - {"memories": [_memory("alpha").model_dump()]}, + json={"memories": [_memory("alpha").model_dump()]}, ) - assert "uuids" in response + assert response.status_code == 200 + payload = response.json() + assert "uuids" in payload - search_response = app.dispatch( - "POST", + search_response = client.post( "/search", - {"query": "alpha", "namespace": "test", "encoder": dummy_encoder, "top_k": 1}, + json={ + "query": "alpha", + "namespace": "test", + "encoder": dummy_encoder, + "top_k": 1, + }, ) - assert search_response["results"] + assert search_response.status_code == 200 + search_payload = search_response.json() + assert search_payload["results"] memory_service.llm_client.calls.clear() - override_response = app.dispatch( - "POST", + override_response = client.post( "/search", - { + json={ "query": "alpha", "namespace": "test", "encoder": dummy_encoder, @@ -78,29 +87,33 @@ def test_rest_stub_routes(memory_service, dummy_encoder): "llm_api_key": "override-key", }, ) - assert override_response["results"] + assert override_response.status_code == 200 + override_payload = override_response.json() + assert override_payload["results"] assert memory_service.llm_client.calls last_call = memory_service.llm_client.calls[-1] assert last_call["model"] == "rerank-dev" assert last_call["base_url"] == "https://llm.example/rerank" assert memory_service.llm_client.last_override["models"]["rerank"] == "rerank-dev" - filtered = app.dispatch( - "GET", + filtered = client.get( "/memories", - {"namespace": "test", "entity_labels": ["Note"]}, + params={"namespace": "test", "entity_labels": ["Note"]}, ) - assert filtered["memories"] + assert filtered.status_code == 200 + filtered_payload = filtered.json() + assert filtered_payload["memories"] - filtered_none = app.dispatch( - "GET", + filtered_none = client.get( "/memories", - {"namespace": "test", "entity_labels": ["Task"]}, + params={"namespace": "test", "entity_labels": ["Task"]}, ) - assert filtered_none["memories"] == [] + assert filtered_none.status_code == 200 + assert filtered_none.json()["memories"] == [] - counts = app.dispatch("GET", "/memories/counts", {"namespace": "test"}) - assert counts["counts"]["test"]["Note"] >= 1 + counts = client.get("/memories/counts", params={"namespace": "test"}) + assert counts.status_code == 200 + assert counts.json()["counts"]["test"]["Note"] >= 1 def test_memory_service_list_memories_forwards_kwargs(memory_service): diff --git a/scripts/check_docs_sync.py b/scripts/check_docs_sync.py index a2495a9..7d5bcbf 100755 --- a/scripts/check_docs_sync.py +++ b/scripts/check_docs_sync.py @@ -18,7 +18,7 @@ "meshmind/pipeline": ["docs/pipelines.md"], "meshmind/core": ["docs/architecture.md", "docs/development.md"], "meshmind/llm_client.py": ["docs/architecture.md", "docs/configuration.md"], - "meshmind/cli": ["docs/operations.md"], + "meshmind/cli": ["docs/operations.md", "docs/api.md"], "meshmind/tasks": ["docs/operations.md", "docs/telemetry.md"], "meshmind/tests/docker": ["SETUP.md", "docs/operations.md"], "docker-compose": ["SETUP.md", "docs/operations.md", "ENVIRONMENT_NEEDS.md"], diff --git a/scripts/check_protos.py b/scripts/check_protos.py index 326ea55..c9898c4 100644 --- a/scripts/check_protos.py +++ b/scripts/check_protos.py @@ -26,6 +26,14 @@ def main() -> None: str(PROTO_FILE), ] subprocess.check_call(command) + grpc_candidate = tmp_path / "memory_service_pb2_grpc.py" + if grpc_candidate.exists(): + text = grpc_candidate.read_text(encoding="utf-8") + text = text.replace( + "import memory_service_pb2 as", + "from . import memory_service_pb2 as", + ) + grpc_candidate.write_text(text, encoding="utf-8") drift = [] for filename in GENERATED: target = PROTO_DIR / filename diff --git a/scripts/generate_protos.py b/scripts/generate_protos.py index e402da0..e1d01fd 100644 --- a/scripts/generate_protos.py +++ b/scripts/generate_protos.py @@ -22,6 +22,16 @@ def main() -> None: str(PROTO_FILE), ] subprocess.check_call(command) + + grpc_file = PROTO_DIR / "memory_service_pb2_grpc.py" + if grpc_file.exists(): + text = grpc_file.read_text(encoding="utf-8") + text = text.replace( + "import memory_service_pb2 as", + "from . import memory_service_pb2 as", + ) + grpc_file.write_text(text, encoding="utf-8") + print("Regenerated protobuf bindings", file=sys.stderr)