diff --git a/.env.example b/.env.example index 373719e..bca7ef3 100644 --- a/.env.example +++ b/.env.example @@ -100,12 +100,20 @@ DEFAULT_MODEL=anthropic/claude-opus-4-6 # but not deep reasoning. Recommended: anthropic/claude-sonnet-4-5-20250929 # or equivalent. # -# Cheap (SUMMARIZATION_MODEL): PR summary generation. Simple synthesis. +# Cheap (SUMMARY_MODEL): PR summary generation. Simple synthesis. # Recommended: anthropic/claude-haiku-4-5-20251001 or equivalent. # +# Mid-tier (MEMORY_DISTILLER_MODEL / MEMORY_CARTOGRAPHER_MODEL): Memory +# reflection — summarizing a trajectory and curating the context memory. +# Requires nuance. +# Recommended: anthropic/claude-sonnet-4-5-20250929 or equivalent. +# # By default, all models fall back to DEFAULT_MODEL. To optimize costs: # EXTRACTION_MODEL=anthropic/claude-sonnet-4-5-20250929 -# SUMMARIZATION_MODEL=anthropic/claude-haiku-4-5-20251001 +# SUMMARY_MODEL=anthropic/claude-haiku-4-5-20251001 +# MEMORY_DISTILLER_MODEL=anthropic/claude-sonnet-4-5-20250929 +# MEMORY_CARTOGRAPHER_MODEL=anthropic/claude-sonnet-4-5-20250929 + # ============================================================================= # Default Settings @@ -116,12 +124,23 @@ DEFAULT_MODEL=anthropic/claude-opus-4-6 # Falls back to DEFAULT_MODEL if not set # EXTRACTION_MODEL=anthropic/claude-sonnet-4-5-20250929 -# DEFAULT_MAX_ITERS=3 -# DEFAULT_MAX_CONTEXT_SIZE=50000 -# Limits LLM reasoning verbosity (helps prevent JSONAdapter failures) -# DEFAULT_MAX_REASONING_TOKENS=1024 -# Lower = more deterministic JSON output -# DEFAULT_TEMPERATURE=0.1 +# DEFAULT_MAX_ITERS=10 +# Provider reasoning budget: minimal | low | medium | high +# DEFAULT_REASONING_EFFORT=medium +# DEFAULT_TEMPERATURE=0.2 + +# Output token budget for a single completion (default: 64000). This is an OUTPUT +# ceiling, not a context window, and reasoning/thinking tokens are charged against +# it — so keep it well above the expected answer size while DEFAULT_REASONING_EFFORT +# is set. codespy always sends an explicit value: without one LiteLLM silently falls +# back to its own 4096 default and truncates responses mid-answer (DSPy warns +# "LM response was truncated ... max_tokens=None"). +# The value is clamped down to each model's real output ceiling before use, so a +# generous setting stays valid on smaller models; models LiteLLM doesn't know +# (Ollama, custom endpoints) receive it unchanged. +# NOTE: LiteLLM also reads DEFAULT_MAX_TOKENS for its own internal fallback, so +# setting this aligns both layers. +# DEFAULT_MAX_TOKENS=64000 # Global LLM reliability settings # Number of retries for LLM API calls @@ -132,6 +151,73 @@ DEFAULT_MODEL=anthropic/claude-opus-4-6 # Enable provider-side prompt caching (reduces latency and costs) # ENABLE_PROMPT_CACHING=true +# ============================================================================= +# Memory (Hippocampus) +# ============================================================================= +# Hippocampus lets scope-based agents (scope, code_review, doc, supply_chain) +# consolidate their run into a ContextMemory and persist it as an Episode. +# Save-only for now (no loading). Disabled by default per-signature — see +# the per-signature MEMORY_* settings below. +# +# Episodes are written to: +# global/episodic///codespy--.json +# under MEMORY_ROOT (filesystem) or MEMORY_S3_BUCKET (s3). + +# Storage backend: filesystem or s3 (default: filesystem) +# MEMORY_BACKEND=filesystem + +# Filesystem backend +# MEMORY_ROOT=~/.cache/codespy/memory + +# S3 backend (used when MEMORY_BACKEND=s3) +# MEMORY_S3_BUCKET=my-bucket +# Falls back to AWS_REGION if not set +# MEMORY_S3_REGION=us-east-1 +# For MinIO / S3-compatible endpoints +# MEMORY_S3_ENDPOINT_URL=https://minio.example.com + +# Reflection defaults — overridable per-signature via _MEMORY_* +# MEMORY_DEFAULT_ENABLED=false +# 0 = reflect once at end_episode +# MEMORY_DEFAULT_MAX_REFLECTS=0 +# +# Four independent token budgets, most to least cost-sensitive: +# +# 1. Ceiling on the rendered ContextMemory. This is the persisted artifact, and it is +# prepended to every agent iteration, so it is re-sent ~DEFAULT_MAX_ITERS times +# per scope. Divided by MEMORY_DEFAULT_MAX_CONTEXT_ITEM_TOKENS it gives the memory's item +# capacity (8192 / 410 ~= 19 items). +# MEMORY_DEFAULT_MAX_CONTEXT_MEMORY_TOKENS=8192 +# 2. Budget for a SINGLE context-memory item, given to the Distiller and the +# Cartographer as a prompt input so no one item eats the whole memory. Soft limit +# (expressed to the LLM, not enforced — truncating an item could corrupt an exact +# constant). Lower it for more, terser items; raise it for fewer, richer ones. +# MEMORY_DEFAULT_MAX_CONTEXT_ITEM_TOKENS=410 +# 3. Cap on the trajectory fed to the Distiller. Tool-using agents can produce +# 100k+ token trajectories and TwoStepAdapter sends the value twice, so keep this +# to ~5-10% of the reflection model's context window. Unset = full trajectory +# (unbounded — not recommended for tool-using agents). +# MEMORY_DEFAULT_MAX_TRAJECTORY_TOKENS=8192 +# 4. Cap on the serialized agent inputs used as the reflection "question". Without +# it, every input field is sent in full (for code review, the complete patch of +# every changed file). Unset = unbounded. +# MEMORY_DEFAULT_MAX_QUESTION_TOKENS=2048 + +# LLM settings for the reflection modules. The Distiller summarizes a trajectory; +# the Cartographer curates the context memory. Both are compact summarize/curate +# tasks, so a cheaper tier than code review usually suffices. +# Each falls back to the corresponding DEFAULT_* value when unset. +# MEMORY_DISTILLER_MODEL=anthropic/claude-haiku-4-5-20251001 +# MEMORY_DISTILLER_REASONING_EFFORT=low +# MEMORY_DISTILLER_TEMPERATURE=1 +# MEMORY_DISTILLER_MAX_TOKENS=64000 + +# MEMORY_CARTOGRAPHER_MODEL=anthropic/claude-haiku-4-5-20251001 +# MEMORY_CARTOGRAPHER_REASONING_EFFORT=low +# MEMORY_CARTOGRAPHER_TEMPERATURE=1 +# MEMORY_CARTOGRAPHER_MAX_TOKENS=64000 + + # ============================================================================= # Output Settings # ============================================================================= @@ -159,31 +245,50 @@ DEFAULT_MODEL=anthropic/claude-opus-4-6 # - DOC (compares patches against documentation) # - SCOPE (code scope detection) # - SUPPLY_CHAIN (supply chain security analysis) -# - SUMMARIZATION (PR summary generation) +# - SUMMARY (PR summary generation) # # Available settings per signature: # - ENABLED (true/false) # - MAX_ITERS (integer) # - MODEL (LiteLLM model string) -# - MAX_CONTEXT_SIZE (integer) -# - MAX_REASONING_TOKENS (integer) - Limits reasoning verbosity -# - TEMPERATURE (float) - Lower = more deterministic output +# - REASONING_EFFORT (minimal|low|medium|high) - Provider reasoning budget +# - TEMPERATURE (float) - Must be 1 while reasoning is enabled +# - MAX_TOKENS (integer) - Output token budget (unset -> DEFAULT_MAX_TOKENS) +# - MEMORY_ENABLED (true/false) - Hippocampus memory (unset -> MEMORY_DEFAULT_ENABLED) +# - MEMORY_MAX_REFLECTS (integer) - Max mid-run reflections (0 = reflect once at end) # Examples: # CODE_REVIEW_ENABLED=true # CODE_REVIEW_MAX_ITERS=10 # CODE_REVIEW_MODEL=anthropic/claude-sonnet-4-5-20250929 -# CODE_REVIEW_MAX_REASONING_TOKENS=512 -# CODE_REVIEW_TEMPERATURE=0.1 +# CODE_REVIEW_REASONING_EFFORT=high +# CODE_REVIEW_TEMPERATURE=1 +# CODE_REVIEW_MAX_TOKENS=64000 + +# CODE_REVIEW_MEMORY_ENABLED=true +# CODE_REVIEW_MEMORY_MAX_REFLECTS=1 # SUPPLY_CHAIN_ENABLED=true +# When true: scans ALL artifacts (Dockerfiles, etc.) and manifests +# When false (default): only scans artifacts/manifests modified in the MR +# SUPPLY_CHAIN_SCAN_UNCHANGED=false +# SUPPLY_CHAIN_MEMORY_ENABLED=true +# SUPPLY_CHAIN_MEMORY_MAX_REFLECTS=1 # DOC_ENABLED=true # DOC_MODEL=anthropic/claude-sonnet-4-5-20250929 +# DOC_MEMORY_ENABLED=true +# DOC_MEMORY_MAX_REFLECTS=1 # SCOPE_ENABLED=true # SCOPE_MAX_ITERS=10 -# SCOPE_MAX_REASONING_TOKENS=1024 +# SCOPE_REASONING_EFFORT=low +# SCOPE_MAX_TOKENS=64000 +# SCOPE_MEMORY_ENABLED=true +# SCOPE_MEMORY_MAX_REFLECTS=1 + +# SUMMARY_ENABLED=true +# SUMMARY_MODEL=anthropic/claude-haiku-4-5-20251001 +# SUMMARY_MEMORY_ENABLED=true +# SUMMARY_MEMORY_MAX_REFLECTS=1 -# SUMMARIZATION_ENABLED=true -# SUMMARIZATION_MODEL=anthropic/claude-haiku-4-5-20251001 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db924cc..234bae7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,6 +130,13 @@ jobs: git tag -f "${{ needs.detect-version-bump.outputs.version }}" git push --force origin "${{ needs.detect-version-bump.outputs.version }}" + - name: Update major version tag + run: | + VERSION="${{ needs.detect-version-bump.outputs.version }}" + MAJOR="${VERSION%%.*}" + git tag -f "v${MAJOR}" + git push --force origin "v${MAJOR}" + - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/codespy-review.yml.example b/.github/workflows/codespy-review.yml.example index 78f8da6..dcb70ac 100644 --- a/.github/workflows/codespy-review.yml.example +++ b/.github/workflows/codespy-review.yml.example @@ -150,4 +150,22 @@ jobs: # if: steps.codespy.outputs.issues-count > 10 # run: | # echo "Too many issues found!" -# exit 1 \ No newline at end of file +# exit 1 + +# --- Using Memory (S3 persistence across runs) --- +# - name: Run CodeSpy Review +# uses: khezen/codespy@main +# with: +# model: 'anthropic/claude-opus-4-6' +# anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} +# # AWS credentials for S3 memory backend +# aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} +# aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} +# aws-region: 'us-east-1' +# # Enable memory globally +# memory-enabled: 'true' +# memory-backend: 's3' +# memory-s3-bucket: 'my-codespy-memory' +# # Use cheap model for reflection +# memory-distiller-model: 'anthropic/claude-haiku-4-5-20251001' +# memory-cartographer-model: 'anthropic/claude-haiku-4-5-20251001' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 363215c..8549e48 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ htmlcov/ .coverage .coverage.* .cache +.ruff_cache nosetests.xml coverage.xml *.cover @@ -54,6 +55,9 @@ coverage.xml # Environments .env +.env.bak* +.env.*.bak +.env.local .venv env/ venv/ @@ -88,4 +92,5 @@ dmypy.json Thumbs.db # Project specific -.cache/ \ No newline at end of file +.cache/ +.kilo/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c169be7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,61 @@ +# Changelog + +## [1.0.0] - 2026-08-18 + +### Added +- Cross-review memory system (Hippocampus) with S3/filesystem storage backends +- Context window overflow resilience (`ContextSafe` wrapper with automatic RLM fallback) +- Scope resolver: deterministic analysis + ReAct agent refinement (replaces `ScopeIdentifier`) +- Patch compaction: expands diff hunks to enclosing function boundaries via Tree-sitter +- Deterministic package manifest parser: extracts package identity from 25+ formats without LLM (npm, Go, pip, Cargo, Maven, Gradle, Composer, Bundler, NuGet, Swift, Pub, Hex, Helm, etc.) +- Tree-sitter extractors for Bash, C++, C#, PHP, Ruby +- Ripgrep fallback extractor for languages without Tree-sitter grammar +- Unified storage abstraction layer (`tools/storage/`) with filesystem and S3 backends +- Audit signature: dedicated module for review quality assessment and recommendation +- Reasoning effort configuration (`minimal|low|medium|high`) — maps to provider-native parameters (Anthropic thinking budget, OpenAI reasoning_effort) +- Per-signature `max_tokens` output token budget (replaces `max_reasoning_tokens`) +- TwoStepAdapter with dedicated extraction model for structured field extraction +- Memory storage access verification (S3/filesystem connectivity check at startup) +- Sparse checkout support in scope resolver for large monorepos +- Deno runtime in Docker image (required by DSPy RLM sandbox) +- Full documentation suite: architecture, configuration, development, memory, usage +- GitHub Action: reasoning effort, summary, and temperature inputs + +### Security +- S3 path traversal hardening: `_resolve_path` decodes percent-encoded input before validation (catches `%2e%2e`, `%2f..%2f` — CWE-22) +- `json-repair` pinned to >=0.56.0 (GHSA-xf7x-x43h-rpqh) +- `litellm` floor raised to ^1.84.0 (excludes known-vulnerable versions) +- `gitpython` floor raised to >=3.1.41 (excludes CVE-affected versions) +- `markdownify` floor raised to >=0.14.0 (excludes known-vulnerable versions) + +### Changed +- **BREAKING**: `MergeRequest` model renamed to `PullRequest` (backward-compat alias removed) +- **BREAKING**: `ReviewContext.merge_request` field → `pull_request` (compat property removed) +- **BREAKING**: CLI argument `mr_url` → `pr_url`; `fetch_merge_request()` → `fetch_pull_request()` +- **BREAKING**: `build_mr_from_diff()` → `build_pr_from_diff()` +- **BREAKING**: `ReviewResult` fields: `mr_number`→`pr_number`, `mr_title`→`pr_title`, `mr_url`→`pr_url` +- **BREAKING**: MCP tool `review_pr` parameter renamed: `mr_url` → `pr_url` +- **BREAKING**: `tools/filesystem` module moved to `tools/storage/filesystem` (import path changed) +- **BREAKING**: Per-signature `max_context_size` and `max_reasoning_tokens` env vars replaced by `reasoning_effort`, `temperature`, and `max_tokens` +- **BREAKING**: GitHub Action inputs removed: `*-max-context-size`, `*-max-reasoning-tokens` (replaced by `*-reasoning-effort`) +- Docker base image: Alpine → Debian slim (glibc required by Deno) +- `dspy` dependency: ^3.1.3 → ^3.3.0 +- `mcp` dependency: >=1.0.0 → >=1.29.0,<2.0.0 +- `litellm` dependency: ^1.81.6 → ^1.84.0 +- `gitpython` dependency: >=3.1.0 → >=3.1.41 +- `json-repair` dependency: ^0.55.1 → >=0.56.0 +- `markdownify` dependency: >=0.13.0 → >=0.14.0 +- ScopeIdentifierSignature → ScopeRefinementSignature (extracted to `ScopeResolver` module) +- MRSummarySignature → PRSummarySignature (extracted to `Summarizer` module with config key `summary`) +- `ReviewMetadata` model introduced to reduce parameter proliferation +- Per-module overflow detection replaced by centralized `ContextSafe` wrapper +- README rewritten: detailed sections moved to `docs/`, simplified TOC +- `codespy.yaml` expanded with memory, reasoning, and per-signature configuration (194 → 326 lines) + +### Removed +- `ScopeIdentifier` module (replaced by `ScopeResolver`) +- `tools/filesystem/__init__.py` (replaced by `tools/storage/` abstraction) +- `default_max_context_size` and `default_max_reasoning_tokens` settings +- Per-signature `MAX_CONTEXT_SIZE` and `MAX_REASONING_TOKENS` env vars (replaced by `REASONING_EFFORT` and `MAX_TOKENS`) +- `MergeRequest` backward-compat alias +- Per-module `_would_overflow_context()` methods diff --git a/Dockerfile b/Dockerfile index ab7589b..f5ad0eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,18 @@ # syntax=docker/dockerfile:1 -# Build stage with Poetry -FROM python:3.11-alpine AS builder +# Build stage +FROM python:3.11-slim AS builder WORKDIR /app -# Install build dependencies for Python packages with native extensions -RUN apk add --no-cache \ +# Install build dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ git \ curl \ gcc \ - musl-dev \ libffi-dev \ - && pip install --no-cache-dir poetry + && pip install --no-cache-dir poetry \ + && rm -rf /var/lib/apt/lists/* # Copy project files COPY pyproject.toml poetry.lock* README.md ./ @@ -23,15 +23,19 @@ RUN poetry config virtualenvs.create false \ && poetry install --only main --no-interaction --no-ansi # Runtime stage -FROM python:3.11-alpine +FROM python:3.11-slim WORKDIR /app # Install runtime dependencies -RUN apk add --no-cache \ +RUN apt-get update && apt-get install -y --no-install-recommends \ git \ ripgrep \ - && adduser -D -u 1000 codespy + && rm -rf /var/lib/apt/lists/* \ + && useradd -m -u 1000 codespy + +# Copy Deno binary (glibc works natively on Debian) +COPY --from=denoland/deno:bin-2.9.5 /deno /usr/local/bin/deno # Copy installed packages from builder COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages @@ -43,19 +47,20 @@ COPY src/ ./src/ # Copy config to user's home directory COPY codespy.yaml /home/codespy/codespy.yaml -# Set up cache directory and DSPy local_cache directory -RUN mkdir -p /home/codespy/.cache/codespy && \ - chown -R codespy:codespy /home/codespy/.cache /home/codespy/codespy.yaml +# Pre-cache Deno/Pyodide dependencies and set up directories +ENV DENO_DIR=/home/codespy/.cache/deno +RUN mkdir -p /home/codespy/.cache/codespy \ + && (deno cache /usr/local/lib/python3.11/site-packages/dspy/primitives/runner.js || true) \ + && chown -R codespy:codespy /home/codespy/.cache /home/codespy/codespy.yaml # Switch to non-root user USER codespy -# Change to writable directory for DSPy's local_cache WORKDIR /home/codespy -# Set environment variables ENV PYTHONUNBUFFERED=1 ENV HOME=/home/codespy +ENV DENO_DIR=/home/codespy/.cache/deno ENTRYPOINT ["codespy"] CMD ["--help"] diff --git a/README.md b/README.md index 2e3fbe1..31280ac 100644 --- a/README.md +++ b/README.md @@ -36,26 +36,7 @@ - [Using Docker](#using-docker) - [Using Poetry (for development)](#using-poetry-for-development) - [Quick Start](#quick-start) -- [Usage](#usage) - - [Command Line](#command-line) - - [IDE Integration (MCP Server)](#ide-integration-mcp-server) - - [Using Docker](#using-docker-1) - - [GitHub Action](#github-action) -- [Configuration](#configuration) - - [Setup](#setup) - - [Git Platform Tokens](#git-platform-tokens) - - [GitHub Token](#github-token) - - [GitLab Token](#gitlab-token) - - [LLM Provider](#llm-provider) - - [Advanced Configuration (YAML)](#advanced-configuration-yaml) - - [Recommended Model Strategy](#recommended-model-strategy) -- [Output](#output) - - [Markdown (default)](#markdown-default) - - [GitHub/GitLab Review Comments](#githubgitlab-review-comments) -- [Architecture](#architecture) -- [DSPy Signatures](#dspy-signatures) -- [Supported Languages](#supported-languages) -- [Development](#development) +- [Documentation](#documentation) - [Contributors](#contributors) - [License](#license) @@ -64,40 +45,37 @@ ## Why CodeSpy? Most AI code reviewers are: -- ❌ Black boxes -- ❌ SaaS-only -- ❌ Opaque about reasoning -- ❌ Risky for sensitive codebases +- ❌ Black boxes +- ❌ SaaS-only +- ❌ Opaque about reasoning +- ❌ Risky for sensitive codebases **CodeSpy is different:** -- 🔍 Transparent reasoning -- 🔐 Self-hostable -- 🧠 Configurable review rules -- 🔄 Native PR integration -- 🧩 Extensible architecture -- 📦 100% open-source +- 🔍 Transparent reasoning +- 🔐 Self-hostable +- 🔄 Native PR integration +- 🧩 Extensible architecture +- 📦 100% open-source Built for **engineering teams that care about correctness, security, and control.** --- - ## Features -- 🔒 **Security Analysis** - Detects common vulnerabilities (injection, auth issues, data exposure, etc.) with CWE references -- 🐛 **Bug Detection** - Identifies logic errors, null references, resource leaks, edge cases -- 📝 **Documentation Review** - Checks for missing docstrings, outdated comments, incomplete docs -- 🔍 **Intelligent Scope Detection** - Automatically identifies code scopes (frontend, backend, infra, microservice in mono repo, etc...) -- 💰 **Cost Tracking** - Track LLM calls, tokens, and costs per review -- 🤖 **Model Agnostic** - Works with OpenAI, AWS Bedrock, Anthropic, Ollama, and more via LiteLLM -- 🐳 **Docker Ready** - Run locally or in the cloud with Docker -- GitHub GitLab **GitHub & GitLab** - Works with both platforms, auto-detects from URL -- 🖥️ **Local Reviews** - Review local git changes without GitHub/GitLab — diff against any branch, ref, or review uncommitted work -- 🧩 **MCP Server** - IDE integration via Model Context Protocol — trigger reviews from AI coding assistants like Cline without leaving your editor -- 🔌 **GitHub Action** - One-line integration for automatic PR reviews - - +- 🔒 **Security Analysis** — Detects common vulnerabilities (injection, auth issues, data exposure) with CWE references +- 🐛 **Bug Detection** — Identifies logic errors, null references, resource leaks, edge cases +- 📝 **Documentation Review** — Checks for missing docstrings, outdated comments, incomplete docs +- 🔍 **Intelligent Scope Detection** — Automatically identifies code scopes (frontend, backend, infra, microservice in monorepo, etc.) +- 🧠 **Cross-Review Memory** — Agents learn patterns, constants, and domain knowledge from past reviews of the same codebase +- 💰 **Cost Tracking** — Track LLM calls, tokens, and costs per review +- 🤖 **Model Agnostic** — Works with OpenAI, AWS Bedrock, Anthropic, Gemini, Ollama, and more via LiteLLM +- 🐳 **Docker Ready** — Run locally or in the cloud with Docker +- GitHub GitLab **GitHub & GitLab** — Works with both platforms, auto-detects from URL +- 🖥️ **Local Reviews** — Review local git changes without GitHub/GitLab — diff against any branch, ref, or review uncommitted work +- 🧩 **MCP Server** — IDE integration via Model Context Protocol — trigger reviews from AI coding assistants without leaving your editor +- 🔌 **GitHub Action** — One-line integration for automatic PR reviews --- @@ -162,580 +140,24 @@ codespy review https://github.com/owner/repo/pull/123 codespy review https://gitlab.com/group/project/-/merge_requests/123 ``` -codespy auto-discovers credentials from standard locations (`~/.aws/credentials`, `gh auth token`, `glab auth token`, etc.) - see [Configuration](#configuration) for details. - ---- - -## Usage - -### Command Line - -```bash -# Review GitHub Pull Request -codespy review https://github.com/owner/repo/pull/123 - -# Review GitLab Merge Request -codespy review https://gitlab.com/group/project/-/merge_requests/123 - -# GitLab with nested groups -codespy review https://gitlab.com/group/subgroup/project/-/merge_requests/123 - -# Self-hosted GitLab -codespy review https://gitlab.mycompany.com/team/project/-/merge_requests/123 - -# Output as JSON -codespy review https://github.com/owner/repo/pull/123 --output json - -# Use a specific model -codespy review https://github.com/owner/repo/pull/123 --model anthropic/claude-opus-4-6 - -# Use a custom config file -codespy review https://github.com/owner/repo/pull/123 --config path/to/config.yaml -codespy review https://github.com/owner/repo/pull/123 -f staging.yaml - -# Disable stdout output (useful with --git-comment) -codespy review https://github.com/owner/repo/pull/123 --no-stdout - -# Post review as GitHub/GitLab comment -codespy review https://github.com/owner/repo/pull/123 --git-comment - -# Combine: only post to Git platform, no stdout -codespy review https://github.com/owner/repo/pull/123 --no-stdout --git-comment - -# Show current configuration -codespy config - -# Show configuration from a specific file -codespy config --config path/to/config.yaml - -# Show version -codespy --version - -# Review local git changes (no GitHub/GitLab needed) -codespy review-local # Review current dir vs main -codespy review-local /path/to/repo # Review specific repo -codespy review-local --base develop # Compare against develop -codespy review-local --base origin/main # Compare against origin/main -codespy review-local --base HEAD~5 # Compare against 5 commits back - -# Review uncommitted changes (staged + unstaged) -codespy review-uncommitted # Review current dir -codespy review-uncommitted /path/to/repo -codespy review-uncommitted --output json -``` - -### IDE Integration (MCP Server) - -CodeSpy can run as an MCP (Model Context Protocol) server for integration with AI coding assistants like Cline, enabling code reviews directly from your editor without leaving your workflow. - -```bash -# Start the MCP server -codespy serve - -# Use a custom config file -codespy serve --config path/to/config.yaml -``` - -**Configure your IDE** (example for Cline in VS Code): - -Add to `cline_mcp_settings.json`: -```json -{ - "mcpServers": { - "codespy-reviewer": { - "command": "codespy", - "args": ["serve"], - "env": { - "DEFAULT_MODEL": "anthropic/claude-opus-4-6", - "ANTHROPIC_API_KEY": "your-key-here" - } - } - } -} -``` - -Or for AWS Bedrock: -```json -{ - "mcpServers": { - "codespy-reviewer": { - "command": "codespy", - "args": ["serve"], - "env": { - "DEFAULT_MODEL": "bedrock/us.anthropic.claude-opus-4-6-v1", - "AWS_REGION": "us-east-1", - "AWS_ACCESS_KEY_ID": "your-access-key", - "AWS_SECRET_ACCESS_KEY": "your-secret-key" - } - } - } -} -``` - -**Available MCP Tools:** -- `review_local_changes(repo_path, base_ref)` — Review branch changes vs base (e.g., vs `main`) -- `review_uncommitted(repo_path)` — Review staged + unstaged working tree changes -- `review_pr(mr_url)` — Review a GitHub PR or GitLab MR by URL - -Then ask your AI assistant: *"Review my local changes"* or *"Review uncommitted work in /path/to/repo"* - -### Using Docker - -```bash -# With docker run (using GHCR image) -docker run --rm \ - -e GITHUB_TOKEN=$GITHUB_TOKEN \ - -e DEFAULT_MODEL=anthropic/claude-opus-4-6 \ - -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ - ghcr.io/khezen/codespy:latest review https://github.com/owner/repo/pull/123 - -# Or use a specific version -docker run --rm \ - -e GITHUB_TOKEN=$GITHUB_TOKEN \ - -e DEFAULT_MODEL=anthropic/claude-opus-4-6 \ - -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ - ghcr.io/khezen/codespy:0.2.1 review https://github.com/owner/repo/pull/123 -``` - -### GitHub Action - -Add CodeSpy to your repository for automatic PR reviews: - -**Trigger on `/codespy review` comment:** - -```yaml -# .github/workflows/codespy-review.yml -name: CodeSpy Code Review - -on: - issue_comment: - types: [created] - -jobs: - review: - # Only run on PR comments containing '/codespy review' - if: | - github.event.issue.pull_request && - contains(github.event.comment.body, '/codespy review') - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - - steps: - - name: Run CodeSpy Review - uses: khezen/codespy@v1 - with: - model: 'anthropic/claude-opus-4-6' - anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} -``` - -**Trigger automatically on every PR:** - -```yaml -# .github/workflows/codespy-review.yml -name: CodeSpy Code Review - -on: - pull_request: - types: [opened, synchronize, reopened] - -jobs: - review: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - - steps: - - name: Run CodeSpy Review - uses: khezen/codespy@v1 - with: - model: 'anthropic/claude-opus-4-6' - anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} -``` - -See [`.github/workflows/codespy-review.yml.example`](.github/workflows/codespy-review.yml.example) for more examples. - ---- - -## Configuration - -codespy supports two configuration methods: -- **`.env` file** - Simple environment variables for basic setup -- **`codespy.yaml`** - Full YAML configuration for advanced options (per-module settings) - -Priority: cmd options > Environment Variables > YAML Config > Defaults - -### Setup - -```bash -# Copy the example file -cp .env.example .env -``` - -### Git Platform Tokens - -codespy automatically detects the platform (GitHub or GitLab) from the URL and discovers tokens from multiple sources. - -#### GitHub Token - -Auto-discovered from: -- `GITHUB_TOKEN` or `GH_TOKEN` environment variables -- GitHub CLI (`gh auth token`) -- Git credential helper -- `~/.netrc` file - -Or create a token at https://github.com/settings/tokens with `repo` scope: -```bash -GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx -``` - -To disable auto-discovery: -```bash -GITHUB_AUTO_DISCOVER_TOKEN=false -``` - -#### GitLab Token - -Auto-discovered from: -- `GITLAB_TOKEN` or `GITLAB_PRIVATE_TOKEN` environment variables -- GitLab CLI (`glab auth token`) -- Git credential helper -- `~/.netrc` file -- python-gitlab config files (`~/.python-gitlab.cfg`, `/etc/python-gitlab.cfg`) - -Or create a token at https://gitlab.com/-/user_settings/personal_access_tokens with `api` scope: -```bash -GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx -``` - -For self-hosted GitLab: -```bash -GITLAB_URL=https://gitlab.mycompany.com -GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx -``` - -To disable auto-discovery: -```bash -GITLAB_AUTO_DISCOVER_TOKEN=false -``` - -### LLM Provider - -codespy auto-discovers credentials for all providers: - -**Anthropic** (auto-discovers from `$ANTHROPIC_API_KEY`, `~/.config/anthropic/`, `~/.anthropic/`): -```bash -DEFAULT_MODEL=anthropic/claude-opus-4-6 -# Optional - set explicitly or let codespy auto-discover: -# ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxx -``` - -**AWS Bedrock** (auto-discovers from `~/.aws/credentials`, AWS CLI, env vars): -```bash -DEFAULT_MODEL=bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0 -AWS_REGION=us-east-1 -# Optional - uses ~/.aws/credentials by default, or set explicitly: -# AWS_ACCESS_KEY_ID=... -# AWS_SECRET_ACCESS_KEY=... -``` - -**OpenAI** (auto-discovers from `$OPENAI_API_KEY`, `~/.config/openai/`, `~/.openai/`): -```bash -DEFAULT_MODEL=openai/gpt-5 -# Optional - set explicitly or let codespy auto-discover: -# OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx -``` - -**Google Gemini** (auto-discovers from `$GEMINI_API_KEY`, `$GOOGLE_API_KEY`, gcloud ADC): -```bash -DEFAULT_MODEL=gemini/gemini-2.5-pro -# Optional - set explicitly or let codespy auto-discover: -# GEMINI_API_KEY=xxxxxxxxxxxxxxxxxxxx -``` - -**Local Ollama:** -```bash -DEFAULT_MODEL=ollama/llama3 -``` - -To disable auto-discovery for specific providers: -```bash -AUTO_DISCOVER_AWS=false -AUTO_DISCOVER_OPENAI=false -AUTO_DISCOVER_ANTHROPIC=false -AUTO_DISCOVER_GEMINI=false -``` - -### Advanced Configuration (YAML) - -For per-signature settings, use `codespy.yaml`. See [`codespy.yaml`](codespy.yaml) for all available options including: -- LLM provider settings and auto-discovery -- Git platform configuration (GitHub/GitLab) -- Per-signature model and iteration overrides -- Output format and destination settings -- Directory exclusions - -Override YAML settings via environment variables using `_` separator: - -```bash -# Default settings -export DEFAULT_MODEL=anthropic/claude-opus-4-6 -export DEFAULT_MAX_ITERS=20 - -# Per-signature settings (use signature name, not module name) -export CODE_REVIEW_MODEL=anthropic/claude-sonnet-4-5-20250929 - -# Output settings -export OUTPUT_STDOUT=false -export OUTPUT_GIT=true -``` - -See `codespy.yaml` for full configuration options. - -### Recommended Model Strategy - -codespy uses a tiered model approach to balance review quality and cost: - -| Tier | Role | Default | Recommended Model | Used By | -|------|------|---------|-------------------|---------| -| 🧠 **Smart** | Core analysis & reasoning | `DEFAULT_MODEL` | `anthropic/claude-opus-4-6` | Code & doc review, supply chain, scope identification | -| ⚡ **Mid-tier** | Field extraction | Falls back to `DEFAULT_MODEL` | `anthropic/claude-sonnet-4-5-20250929` | TwoStepAdapter field extraction | -| 💰 **Cheap** | Summarization | Falls back to `DEFAULT_MODEL` | `anthropic/claude-haiku-4-5-20251001` | PR summary generation | - -By default, **all models use `DEFAULT_MODEL`** (`anthropic/claude-opus-4-6`). This works out of the box — just set your API credentials and go. - -To optimize costs, override the mid-tier and cheap models: - -```bash -# .env or environment variables -DEFAULT_MODEL=anthropic/claude-opus-4-6 # Smart tier (default) -EXTRACTION_MODEL=anthropic/claude-sonnet-4-5-20250929 # Mid-tier: field extraction -SUMMARIZATION_MODEL=anthropic/claude-haiku-4-5-20251001 # Cheap tier: PR summary -``` - -Or in `codespy.yaml`: - -```yaml -default_model: anthropic/claude-opus-4-6 -extraction_model: anthropic/claude-sonnet-4-5-20250929 -signatures: - summarization: - model: anthropic/claude-haiku-4-5-20251001 -``` +codespy auto-discovers credentials from standard locations (`~/.aws/credentials`, `gh auth token`, `glab auth token`, etc.) - see [Configuration](docs/configuration.md) for details. --- -## Output - -### Markdown (default) - -```markdown -# Code Review: Add user authentication - -**PR:** [owner/repo#123](https://github.com/owner/repo/pull/123) -**Reviewed at:** 2024-01-15 10:30 UTC -**Model:** anthropic/claude-opus-4-6 - -## Summary - -This PR implements user authentication with JWT tokens... - -## Statistics +## Documentation -- **Total Issues:** 3 -- **Critical:** 1 -- **Security:** 1 -- **Bugs:** 1 -- **Documentation:** 1 - -## Issues - -### 🔴 Critical (1) - -#### SQL Injection Vulnerability - -**Location:** `src/auth/login.py:45` -**Category:** security - -The user input is directly interpolated into the SQL query... - -**Code:** -query = f"SELECT * FROM users WHERE username = '{username}'" - -**Suggestion:** -Use parameterized queries instead... - -**Reference:** [CWE-89](https://cwe.mitre.org/data/definitions/89.html) - -``` - -### GitHub/GitLab Review Comments - -CodeSpy can post reviews directly to GitHub PRs or GitLab MRs as native review comments with inline annotations. - -**Enable via CLI:** -```bash -# GitHub -codespy review https://github.com/owner/repo/pull/123 --git-comment - -# GitLab -codespy review https://gitlab.com/group/project/-/merge_requests/123 --git-comment - -# Combine: only post to platform, no stdout -codespy review https://github.com/owner/repo/pull/123 --no-stdout --git-comment -``` - -**Enable via configuration:** -```bash -# Environment variable -export OUTPUT_GIT=true - -# Or in codespy.yaml -output_git: true -``` - -**Features:** - -- 🎯 **Inline Comments** - Issues are posted as review comments on the exact lines where they occur -- 📏 **Multi-line Support** - Issues spanning multiple lines are annotated with start/end line ranges -- 🔴🟠🟡🔵 **Severity Indicators** - Visual emoji markers for Critical, High, Medium, Low severity -- 📦 **Collapsible Sections** - Organized review body with expandable details: - - 📋 Summary of changes - - 🎯 Quality Assessment - - 📊 Statistics table - - 💰 Cost breakdown per signature - - 💡 Recommendation -- 🔗 **CWE References** - Security issues link directly to MITRE CWE database - ---- - -## Architecture - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ codespy CLI │ -├─────────────────────────────────────────────────────────────────────┤ -│ review [--config ...] [--output json|md] [--model ...] │ -└──────────────────────────────┬──────────────────────────────────────┘ - │ -┌──────────────────────────────▼──────────────────────────────────────┐ -│ Git Platform Integration │ -│ - GitHub: Fetch PR diff, changed files, commit messages │ -│ - GitLab: Fetch MR diff, changed files, commit messages │ -│ - Auto-detects platform from URL │ -│ - Clone/access full repository for context │ -└──────────────────────────────┬──────────────────────────────────────┘ - │ -┌──────────────────────────────▼──────────────────────────────────────┐ -│ DSPy Review Pipeline │ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ Scope Identifier │ │ -│ │ (identifies code scopes: frontend, backend, infra, etc.) │ │ -│ └──────────────────────────┬─────────────────────────────────┘ │ -│ │ │ -│ ┌──────────────────────────▼─────────────────────────────────┐ │ -│ │ Parallel Review Modules │ │ -│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │ │ -│ │ │ Supply Chain │ │ Code │ │ Doc │ │ │ -│ │ │ Auditor │ │ Reviewer │ │ Reviewer │ │ │ -│ │ │ │ │ (bug+sec+ │ │ │ │ │ -│ │ │ │ │ smell) │ │ │ │ │ -│ │ └──────────────┘ └──────────────┘ └──────────┘ │ │ -│ └──────────────────────────┬─────────────────────────────────┘ │ -│ │ │ -│ ┌──────────────────────────▼─────────────────────────────────┐ │ -│ │ PR Summarizer │ │ -│ │ (generates summary, quality assessment, recommendation) │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -│ Cost Tracker (tokens, calls, $) │ -└──────────────────────────────┬──────────────────────────────────────┘ - │ -┌──────────────────────────────▼──────────────────────────────────────┐ -│ Tools Layer │ -│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │ -│ │ Filesystem │ │ Git │ │ Web │ │ Cyber/OSV │ │ -│ │ │ │ (GH + GL) │ │ │ │ │ │ -│ └────────────┘ └────────────┘ └────────────┘ └──────────────┘ │ -│ ┌────────────────────────────────────────────────────────────────┐ │ -│ │ Parsers │ │ -│ │ ┌─────────────────┐ ┌────────────────────────────────────┐ │ │ -│ │ │ Ripgrep │ │ Tree-sitter │ │ │ -│ │ │ (code search) │ │ (multi-language AST parsing) │ │ │ -│ │ └─────────────────┘ └────────────────────────────────────┘ │ │ -│ └────────────────────────────────────────────────────────────────┘ │ -└──────────────────────────────┬──────────────────────────────────────┘ - │ -┌──────────────────────────────▼──────────────────────────────────────┐ -│ LLM Backend (LiteLLM) │ -│ Bedrock | OpenAI | Anthropic | Ollama | Any OpenAI-compatible │ -└─────────────────────────────────────────────────────────────────────┘ -``` - -## DSPy Signatures - -The review is powered by DSPy signatures that structure the LLM's analysis: - -| Signature | Config Key | Description | -|-----------|------------|-------------| -| **ScopeIdentifierSignature** | `scope` | Identifies code scopes (frontend, backend, infra, microservice in mono repo, etc...) | -| **CodeReviewSignature** | `code_review` | Detects verified bugs, security vulnerabilities, removed defensive code, and code smells | -| **DocReviewSignature** | `doc` | Detects stale or wrong documentation caused by code changes | -| **SupplyChainSecuritySignature** | `supply_chain` | Analyzes artifacts (Dockerfiles) and dependencies for supply chain security | -| **MRSummarySignature** | `summarization` | Generates summary, quality assessment, and recommendation | - -## Supported Languages - -Tree-sitter based parsing for context-aware analysis: - -| Language | Extensions | Features | -|----------|-----------|----------| -| Python | `.py` | Functions, classes, imports | -| JavaScript | `.js`, `.jsx` | Functions, classes, imports | -| TypeScript | `.ts`, `.tsx` | Functions, classes, interfaces | -| Go | `.go` | Functions, structs, interfaces | -| Java | `.java` | Methods, classes, packages | -| Kotlin | `.kt` | Functions, classes, objects | -| Swift | `.swift` | Functions, classes, structs | -| Objective-C | `.m`, `.h` | Methods, interfaces, protocols | -| Rust | `.rs` | Functions, structs, traits, impl blocks | -| Terraform | `.tf` | Resources, data sources, modules, variables | - -All languages are supported for security, bug, and documentation analysis. - -## Development - -```bash -# Quick setup (creates .env and installs dependencies) -make setup - -# Or manually with Poetry: -poetry install # Install all dependencies including dev -poetry lock # Update lock file - -# Available make targets -make help - -# Run commands with Poetry -make lint # Run ruff linter -make format # Format code with ruff -make typecheck # Run mypy type checker -make test # Run pytest tests -make build # Build package with Poetry -make clean # Clean build artifacts - -# Or run directly: -poetry run codespy review https://github.com/owner/repo/pull/123 -poetry run ruff check src/ -poetry run mypy src/ -``` +| Guide | Contents | +|-------|----------| +| **[Usage](docs/usage.md)** | CLI commands, Docker, GitHub Action, MCP server, output formats | +| **[Configuration](docs/configuration.md)** | Environment variables, YAML config, model strategy, per-signature settings | +| **[Architecture](docs/architecture.md)** | Pipeline design, DSPy signatures, supported languages | +| **[Memory System](docs/memory.md)** | Hippocampus episodic memory for cross-review knowledge | +| **[Development](docs/development.md)** | Setup, build, test, lint | --- ## Contributors + * @khezen * @pranavsriram8 @@ -743,4 +165,4 @@ poetry run mypy src/ ## License -MIT \ No newline at end of file +MIT diff --git a/action.yml b/action.yml index a542616..e7dbc9f 100644 --- a/action.yml +++ b/action.yml @@ -68,20 +68,16 @@ inputs: required: false default: '3' - default-max-context-size: - description: 'Default maximum context size for all signatures' + default-reasoning-effort: + description: 'Default reasoning effort for all signatures (minimal|low|medium|high)' required: false - default: '50000' - - default-max-reasoning-tokens: - description: 'Default maximum reasoning tokens for all signatures' - required: false - default: '8000' + default: 'medium' default-temperature: - description: 'Default temperature for LLM calls (0.0-1.0)' + description: 'Default temperature for LLM calls' required: false - default: '0.1' + default: '0.2' + llm-retries: description: 'Number of retries for LLM API calls' @@ -114,12 +110,8 @@ inputs: description: 'Max iterations for scope identification' required: false - scope-max-context-size: - description: 'Max context size for scope identification' - required: false - - scope-max-reasoning-tokens: - description: 'Max reasoning tokens for scope identification' + scope-reasoning-effort: + description: 'Reasoning effort for scope (minimal|low|medium|high)' required: false scope-temperature: @@ -142,12 +134,8 @@ inputs: description: 'Max iterations for code review' required: false - code-review-max-context-size: - description: 'Max context size for code review' - required: false - - code-review-max-reasoning-tokens: - description: 'Max reasoning tokens for code review' + code-review-reasoning-effort: + description: 'Reasoning effort for code review (minimal|low|medium|high)' required: false code-review-temperature: @@ -166,16 +154,8 @@ inputs: description: 'Model for doc review (empty = use default)' required: false - doc-max-iters: - description: 'Max iterations for doc review' - required: false - - doc-max-context-size: - description: 'Max context size for doc review' - required: false - - doc-max-reasoning-tokens: - description: 'Max reasoning tokens for doc review' + doc-reasoning-effort: + description: 'Reasoning effort for doc (minimal|low|medium|high)' required: false doc-temperature: @@ -198,12 +178,8 @@ inputs: description: 'Max iterations for supply chain security' required: false - supply-chain-max-context-size: - description: 'Max context size for supply chain security' - required: false - - supply-chain-max-reasoning-tokens: - description: 'Max reasoning tokens for supply chain security' + supply-chain-reasoning-effort: + description: 'Reasoning effort for supply chain (minimal|low|medium|high)' required: false supply-chain-temperature: @@ -216,33 +192,104 @@ inputs: default: 'false' # ========================================== - # SIGNATURE: summarization + # SIGNATURE: summary # ========================================== - summarization-enabled: - description: 'Enable PR summarization signature' + summary-enabled: + description: 'Enable PR summary signature' required: false default: 'true' - - summarization-model: - description: 'Model for summarization (empty = use default)' + + summary-model: + description: 'Model for summary (empty = use default)' required: false - - summarization-max-iters: - description: 'Max iterations for summarization' + + summary-reasoning-effort: + description: 'Reasoning effort for summary (minimal|low|medium|high)' required: false - - summarization-max-context-size: - description: 'Max context size for summarization' + + summary-temperature: + description: 'Temperature for summary' required: false - - summarization-max-reasoning-tokens: - description: 'Max reasoning tokens for summarization' + + # ========================================== + # MEMORY (Hippocampus) + # ========================================== + memory-enabled: + description: 'Enable memory (Hippocampus) globally for cross-run learning (default: false)' required: false - - summarization-temperature: - description: 'Temperature for summarization' + default: 'false' + + memory-backend: + description: 'Memory storage backend: filesystem or s3. Note: filesystem is ephemeral in Docker; use s3 for persistence across runs.' required: false - + default: 'filesystem' + + memory-root: + description: 'Filesystem path for memory storage (only used with filesystem backend; ephemeral in Docker)' + required: false + + memory-s3-bucket: + description: 'S3 bucket for memory storage (required when backend is s3). Requires aws-access-key-id/aws-secret-access-key inputs.' + required: false + + memory-s3-region: + description: 'S3 region for memory bucket (defaults to aws-region input)' + required: false + + memory-s3-endpoint-url: + description: 'S3 endpoint URL for S3-compatible stores (e.g., MinIO)' + required: false + + memory-max-reflects: + description: 'Max online reflections per agent call (0 = batch-only, default: 0)' + required: false + default: '0' + + memory-distiller-model: + description: 'Model for the Distiller reflection module (smaller model recommended)' + required: false + + memory-distiller-reasoning-effort: + description: 'Reasoning effort for Distiller (minimal|low|medium|high)' + required: false + + memory-distiller-temperature: + description: 'Temperature for Distiller' + required: false + + memory-cartographer-model: + description: 'Model for the Cartographer reflection module (smaller model recommended)' + required: false + + memory-cartographer-reasoning-effort: + description: 'Reasoning effort for Cartographer (minimal|low|medium|high)' + required: false + + memory-cartographer-temperature: + description: 'Temperature for Cartographer' + required: false + + # Per-signature memory overrides + scope-memory-enabled: + description: 'Enable memory for scope identification (overrides global memory-enabled)' + required: false + + code-review-memory-enabled: + description: 'Enable memory for code review (overrides global memory-enabled)' + required: false + + doc-memory-enabled: + description: 'Enable memory for doc review (overrides global memory-enabled)' + required: false + + supply-chain-memory-enabled: + description: 'Enable memory for supply chain (overrides global memory-enabled)' + required: false + + summary-memory-enabled: + description: 'Enable memory for summary (overrides global memory-enabled)' + required: false + excluded-directories: description: 'JSON array of directories to exclude from review (e.g., ["vendor", "dist"])' required: false @@ -309,8 +356,7 @@ runs: # Global defaults DEFAULT_MAX_ITERS: ${{ inputs.default-max-iters }} - DEFAULT_MAX_CONTEXT_SIZE: ${{ inputs.default-max-context-size }} - DEFAULT_MAX_REASONING_TOKENS: ${{ inputs.default-max-reasoning-tokens }} + DEFAULT_REASONING_EFFORT: ${{ inputs.default-reasoning-effort }} DEFAULT_TEMPERATURE: ${{ inputs.default-temperature }} LLM_RETRIES: ${{ inputs.llm-retries }} LLM_TIMEOUT: ${{ inputs.llm-timeout }} @@ -320,45 +366,58 @@ runs: SCOPE_ENABLED: ${{ inputs.scope-enabled }} SCOPE_MODEL: ${{ inputs.scope-model }} SCOPE_MAX_ITERS: ${{ inputs.scope-max-iters }} - SCOPE_MAX_CONTEXT_SIZE: ${{ inputs.scope-max-context-size }} - SCOPE_MAX_REASONING_TOKENS: ${{ inputs.scope-max-reasoning-tokens }} + SCOPE_REASONING_EFFORT: ${{ inputs.scope-reasoning-effort }} SCOPE_TEMPERATURE: ${{ inputs.scope-temperature }} # Code review signature CODE_REVIEW_ENABLED: ${{ inputs.code-review-enabled }} CODE_REVIEW_MODEL: ${{ inputs.code-review-model }} CODE_REVIEW_MAX_ITERS: ${{ inputs.code-review-max-iters }} - CODE_REVIEW_MAX_CONTEXT_SIZE: ${{ inputs.code-review-max-context-size }} - CODE_REVIEW_MAX_REASONING_TOKENS: ${{ inputs.code-review-max-reasoning-tokens }} + CODE_REVIEW_REASONING_EFFORT: ${{ inputs.code-review-reasoning-effort }} CODE_REVIEW_TEMPERATURE: ${{ inputs.code-review-temperature }} # Doc review signature DOC_ENABLED: ${{ inputs.doc-enabled }} DOC_MODEL: ${{ inputs.doc-model }} - DOC_MAX_ITERS: ${{ inputs.doc-max-iters }} - DOC_MAX_CONTEXT_SIZE: ${{ inputs.doc-max-context-size }} - DOC_MAX_REASONING_TOKENS: ${{ inputs.doc-max-reasoning-tokens }} + DOC_REASONING_EFFORT: ${{ inputs.doc-reasoning-effort }} DOC_TEMPERATURE: ${{ inputs.doc-temperature }} # Supply chain signature SUPPLY_CHAIN_ENABLED: ${{ inputs.supply-chain-enabled }} SUPPLY_CHAIN_MODEL: ${{ inputs.supply-chain-model }} SUPPLY_CHAIN_MAX_ITERS: ${{ inputs.supply-chain-max-iters }} - SUPPLY_CHAIN_MAX_CONTEXT_SIZE: ${{ inputs.supply-chain-max-context-size }} - SUPPLY_CHAIN_MAX_REASONING_TOKENS: ${{ inputs.supply-chain-max-reasoning-tokens }} + SUPPLY_CHAIN_REASONING_EFFORT: ${{ inputs.supply-chain-reasoning-effort }} SUPPLY_CHAIN_TEMPERATURE: ${{ inputs.supply-chain-temperature }} SUPPLY_CHAIN_SCAN_UNCHANGED: ${{ inputs.supply-chain-scan-unchanged }} - # Summarization signature - SUMMARIZATION_ENABLED: ${{ inputs.summarization-enabled }} - SUMMARIZATION_MODEL: ${{ inputs.summarization-model }} - SUMMARIZATION_MAX_ITERS: ${{ inputs.summarization-max-iters }} - SUMMARIZATION_MAX_CONTEXT_SIZE: ${{ inputs.summarization-max-context-size }} - SUMMARIZATION_MAX_REASONING_TOKENS: ${{ inputs.summarization-max-reasoning-tokens }} - SUMMARIZATION_TEMPERATURE: ${{ inputs.summarization-temperature }} + # Summary signature + SUMMARY_ENABLED: ${{ inputs.summary-enabled }} + SUMMARY_MODEL: ${{ inputs.summary-model }} + SUMMARY_REASONING_EFFORT: ${{ inputs.summary-reasoning-effort }} + SUMMARY_TEMPERATURE: ${{ inputs.summary-temperature }} # Other settings EXCLUDED_DIRECTORIES: ${{ inputs.excluded-directories }} + + # Memory (Hippocampus) + MEMORY_DEFAULT_ENABLED: ${{ inputs.memory-enabled }} + MEMORY_BACKEND: ${{ inputs.memory-backend }} + MEMORY_ROOT: ${{ inputs.memory-root }} + MEMORY_S3_BUCKET: ${{ inputs.memory-s3-bucket }} + MEMORY_S3_REGION: ${{ inputs.memory-s3-region }} + MEMORY_S3_ENDPOINT_URL: ${{ inputs.memory-s3-endpoint-url }} + MEMORY_DEFAULT_MAX_REFLECTS: ${{ inputs.memory-max-reflects }} + MEMORY_DISTILLER_MODEL: ${{ inputs.memory-distiller-model }} + MEMORY_DISTILLER_REASONING_EFFORT: ${{ inputs.memory-distiller-reasoning-effort }} + MEMORY_DISTILLER_TEMPERATURE: ${{ inputs.memory-distiller-temperature }} + MEMORY_CARTOGRAPHER_MODEL: ${{ inputs.memory-cartographer-model }} + MEMORY_CARTOGRAPHER_REASONING_EFFORT: ${{ inputs.memory-cartographer-reasoning-effort }} + MEMORY_CARTOGRAPHER_TEMPERATURE: ${{ inputs.memory-cartographer-temperature }} + SCOPE_MEMORY_ENABLED: ${{ inputs.scope-memory-enabled }} + CODE_REVIEW_MEMORY_ENABLED: ${{ inputs.code-review-memory-enabled }} + DOC_MEMORY_ENABLED: ${{ inputs.doc-memory-enabled }} + SUPPLY_CHAIN_MEMORY_ENABLED: ${{ inputs.supply-chain-memory-enabled }} + SUMMARY_MEMORY_ENABLED: ${{ inputs.summary-memory-enabled }} run: | set -eo pipefail @@ -377,8 +436,7 @@ runs: # Global defaults [ -n "$DEFAULT_MAX_ITERS" ] && DOCKER_ARGS="$DOCKER_ARGS -e DEFAULT_MAX_ITERS" - [ -n "$DEFAULT_MAX_CONTEXT_SIZE" ] && DOCKER_ARGS="$DOCKER_ARGS -e DEFAULT_MAX_CONTEXT_SIZE" - [ -n "$DEFAULT_MAX_REASONING_TOKENS" ] && DOCKER_ARGS="$DOCKER_ARGS -e DEFAULT_MAX_REASONING_TOKENS" + [ -n "$DEFAULT_REASONING_EFFORT" ] && DOCKER_ARGS="$DOCKER_ARGS -e DEFAULT_REASONING_EFFORT" [ -n "$DEFAULT_TEMPERATURE" ] && DOCKER_ARGS="$DOCKER_ARGS -e DEFAULT_TEMPERATURE" [ -n "$LLM_RETRIES" ] && DOCKER_ARGS="$DOCKER_ARGS -e LLM_RETRIES" [ -n "$LLM_TIMEOUT" ] && DOCKER_ARGS="$DOCKER_ARGS -e LLM_TIMEOUT" @@ -388,46 +446,59 @@ runs: [ -n "$SCOPE_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e SCOPE_ENABLED" [ -n "$SCOPE_MODEL" ] && DOCKER_ARGS="$DOCKER_ARGS -e SCOPE_MODEL" [ -n "$SCOPE_MAX_ITERS" ] && DOCKER_ARGS="$DOCKER_ARGS -e SCOPE_MAX_ITERS" - [ -n "$SCOPE_MAX_CONTEXT_SIZE" ] && DOCKER_ARGS="$DOCKER_ARGS -e SCOPE_MAX_CONTEXT_SIZE" - [ -n "$SCOPE_MAX_REASONING_TOKENS" ] && DOCKER_ARGS="$DOCKER_ARGS -e SCOPE_MAX_REASONING_TOKENS" + [ -n "$SCOPE_REASONING_EFFORT" ] && DOCKER_ARGS="$DOCKER_ARGS -e SCOPE_REASONING_EFFORT" [ -n "$SCOPE_TEMPERATURE" ] && DOCKER_ARGS="$DOCKER_ARGS -e SCOPE_TEMPERATURE" # Code review [ -n "$CODE_REVIEW_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e CODE_REVIEW_ENABLED" [ -n "$CODE_REVIEW_MODEL" ] && DOCKER_ARGS="$DOCKER_ARGS -e CODE_REVIEW_MODEL" [ -n "$CODE_REVIEW_MAX_ITERS" ] && DOCKER_ARGS="$DOCKER_ARGS -e CODE_REVIEW_MAX_ITERS" - [ -n "$CODE_REVIEW_MAX_CONTEXT_SIZE" ] && DOCKER_ARGS="$DOCKER_ARGS -e CODE_REVIEW_MAX_CONTEXT_SIZE" - [ -n "$CODE_REVIEW_MAX_REASONING_TOKENS" ] && DOCKER_ARGS="$DOCKER_ARGS -e CODE_REVIEW_MAX_REASONING_TOKENS" + [ -n "$CODE_REVIEW_REASONING_EFFORT" ] && DOCKER_ARGS="$DOCKER_ARGS -e CODE_REVIEW_REASONING_EFFORT" [ -n "$CODE_REVIEW_TEMPERATURE" ] && DOCKER_ARGS="$DOCKER_ARGS -e CODE_REVIEW_TEMPERATURE" # Doc review [ -n "$DOC_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e DOC_ENABLED" [ -n "$DOC_MODEL" ] && DOCKER_ARGS="$DOCKER_ARGS -e DOC_MODEL" - [ -n "$DOC_MAX_ITERS" ] && DOCKER_ARGS="$DOCKER_ARGS -e DOC_MAX_ITERS" - [ -n "$DOC_MAX_CONTEXT_SIZE" ] && DOCKER_ARGS="$DOCKER_ARGS -e DOC_MAX_CONTEXT_SIZE" - [ -n "$DOC_MAX_REASONING_TOKENS" ] && DOCKER_ARGS="$DOCKER_ARGS -e DOC_MAX_REASONING_TOKENS" + [ -n "$DOC_REASONING_EFFORT" ] && DOCKER_ARGS="$DOCKER_ARGS -e DOC_REASONING_EFFORT" [ -n "$DOC_TEMPERATURE" ] && DOCKER_ARGS="$DOCKER_ARGS -e DOC_TEMPERATURE" # Supply chain [ -n "$SUPPLY_CHAIN_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUPPLY_CHAIN_ENABLED" [ -n "$SUPPLY_CHAIN_MODEL" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUPPLY_CHAIN_MODEL" [ -n "$SUPPLY_CHAIN_MAX_ITERS" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUPPLY_CHAIN_MAX_ITERS" - [ -n "$SUPPLY_CHAIN_MAX_CONTEXT_SIZE" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUPPLY_CHAIN_MAX_CONTEXT_SIZE" - [ -n "$SUPPLY_CHAIN_MAX_REASONING_TOKENS" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUPPLY_CHAIN_MAX_REASONING_TOKENS" + [ -n "$SUPPLY_CHAIN_REASONING_EFFORT" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUPPLY_CHAIN_REASONING_EFFORT" [ -n "$SUPPLY_CHAIN_TEMPERATURE" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUPPLY_CHAIN_TEMPERATURE" [ -n "$SUPPLY_CHAIN_SCAN_UNCHANGED" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUPPLY_CHAIN_SCAN_UNCHANGED" - # Summarization - [ -n "$SUMMARIZATION_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUMMARIZATION_ENABLED" - [ -n "$SUMMARIZATION_MODEL" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUMMARIZATION_MODEL" - [ -n "$SUMMARIZATION_MAX_ITERS" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUMMARIZATION_MAX_ITERS" - [ -n "$SUMMARIZATION_MAX_CONTEXT_SIZE" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUMMARIZATION_MAX_CONTEXT_SIZE" - [ -n "$SUMMARIZATION_MAX_REASONING_TOKENS" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUMMARIZATION_MAX_REASONING_TOKENS" - [ -n "$SUMMARIZATION_TEMPERATURE" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUMMARIZATION_TEMPERATURE" + # Summary + [ -n "$SUMMARY_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUMMARY_ENABLED" + [ -n "$SUMMARY_MODEL" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUMMARY_MODEL" + [ -n "$SUMMARY_REASONING_EFFORT" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUMMARY_REASONING_EFFORT" + [ -n "$SUMMARY_TEMPERATURE" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUMMARY_TEMPERATURE" # Other settings [ -n "$EXCLUDED_DIRECTORIES" ] && DOCKER_ARGS="$DOCKER_ARGS -e EXCLUDED_DIRECTORIES" - + + # Memory (Hippocampus) + [ -n "$MEMORY_DEFAULT_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_DEFAULT_ENABLED" + [ -n "$MEMORY_BACKEND" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_BACKEND" + [ -n "$MEMORY_ROOT" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_ROOT" + [ -n "$MEMORY_S3_BUCKET" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_S3_BUCKET" + [ -n "$MEMORY_S3_REGION" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_S3_REGION" + [ -n "$MEMORY_S3_ENDPOINT_URL" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_S3_ENDPOINT_URL" + [ -n "$MEMORY_DEFAULT_MAX_REFLECTS" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_DEFAULT_MAX_REFLECTS" + [ -n "$MEMORY_DISTILLER_MODEL" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_DISTILLER_MODEL" + [ -n "$MEMORY_DISTILLER_REASONING_EFFORT" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_DISTILLER_REASONING_EFFORT" + [ -n "$MEMORY_DISTILLER_TEMPERATURE" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_DISTILLER_TEMPERATURE" + [ -n "$MEMORY_CARTOGRAPHER_MODEL" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_CARTOGRAPHER_MODEL" + [ -n "$MEMORY_CARTOGRAPHER_REASONING_EFFORT" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_CARTOGRAPHER_REASONING_EFFORT" + [ -n "$MEMORY_CARTOGRAPHER_TEMPERATURE" ] && DOCKER_ARGS="$DOCKER_ARGS -e MEMORY_CARTOGRAPHER_TEMPERATURE" + [ -n "$SCOPE_MEMORY_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e SCOPE_MEMORY_ENABLED" + [ -n "$CODE_REVIEW_MEMORY_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e CODE_REVIEW_MEMORY_ENABLED" + [ -n "$DOC_MEMORY_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e DOC_MEMORY_ENABLED" + [ -n "$SUPPLY_CHAIN_MEMORY_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUPPLY_CHAIN_MEMORY_ENABLED" + [ -n "$SUMMARY_MEMORY_ENABLED" ] && DOCKER_ARGS="$DOCKER_ARGS -e SUMMARY_MEMORY_ENABLED" + # Build codespy command arguments CMD_ARGS="review ${{ steps.pr-url.outputs.url }}" CMD_ARGS="$CMD_ARGS --output ${{ inputs.output-format }}" diff --git a/codespy.yaml b/codespy.yaml index 516f0bb..3f345ab 100644 --- a/codespy.yaml +++ b/codespy.yaml @@ -57,11 +57,96 @@ gitlab: url: https://gitlab.com # GITLAB_URL (for self-hosted instances) auto_discover_token: true # GITLAB_AUTO_DISCOVER_TOKEN (set to false to disable auto-discovery) +# ============================================================================ +# MEMORY +# ============================================================================ +# Hippocampus lets scope-based agents (scope, code_review, doc, supply_chain) +# and the summary step consolidate their run into a ContextMemory and +# persist it as an Episode. Save-only for now (no loading). Disabled by +# default globally; enabled by default for summary — see `memory:` +# blocks under each signature below. + +# +# Episodes are written to: +# global/episodic///codespy--.json +# under `root` (filesystem) or `s3_bucket` (s3). +memory: + backend: filesystem # MEMORY_BACKEND (filesystem | s3) + + # Filesystem backend + root: ~/.cache/codespy/memory # MEMORY_ROOT + + # S3 backend (used when backend: s3) + # --------------------------------------------------------------------------- + # Use this to store memory episodes in S3-compatible object storage instead of + # the local filesystem. Useful for: + # - Shared memory across multiple codespy instances (CI/CD, distributed setups) + # - Centralized episode storage + # - Integration with MinIO, AWS S3, or other S3-compatible services + # --------------------------------------------------------------------------- + s3_bucket: null # MEMORY_S3_BUCKET - The bucket name + # Example: "codespy-memory" or "mycompany-codespy" + + s3_region: null # MEMORY_S3_REGION - AWS region for the bucket + # Falls back to aws_region (from llm.* section above) + # Example: "us-east-1", "eu-west-1" + + s3_endpoint_url: null # MEMORY_S3_ENDPOINT_URL - Custom endpoint + # Only needed for MinIO or non-AWS S3-compatible storage + # Leave as null for AWS S3 + # Example: "http://localhost:9000" (MinIO local) + # Example: "https://minio.example.com" (MinIO remote) + + # Reflection defaults — overridable per-signature via signatures..memory + default_enabled: false # MEMORY_DEFAULT_ENABLED + default_max_reflects: 0 # MEMORY_DEFAULT_MAX_REFLECTS (0 = reflect once at end_episode) + + # Four independent token budgets, from most to least cost-sensitive: + # + # 1. max_context_memory_tokens — ceiling on the rendered ContextMemory. This is the + # persisted artifact, and it is prepended to every agent iteration, so it is + # re-sent ~default_max_iters times per scope. Divided by max_context_item_tokens it + # gives the memory's item capacity (8192 / 410 ~= 19 items). + # 2. max_context_item_tokens — budget for a SINGLE context memory item, given to the Distiller + # and the Cartographer as a prompt input so no one item eats the whole memory. + # Soft limit (expressed to the LLM, not enforced — truncating an item could + # corrupt an exact constant). Lower it for more, terser items; raise it for + # fewer, richer ones. + # 3. max_trajectory_tokens — cap on the trajectory fed to the Distiller. Tool-using + # agents can produce 100k+ token trajectories and TwoStepAdapter sends the value + # twice, so keep this to ~5-10% of the reflection model's context window. + # null = full trajectory (unbounded — not recommended for tool-using agents). + # 4. max_question_tokens — cap on the serialized agent inputs used as the reflection + # "question". Without it, every input field is sent in full (for code review that + # means the complete patch of every changed file). null = unbounded. + default_max_context_memory_tokens: 8192 # MEMORY_DEFAULT_MAX_CONTEXT_MEMORY_TOKENS + default_max_context_item_tokens: 410 # MEMORY_DEFAULT_MAX_CONTEXT_ITEM_TOKENS + default_max_trajectory_tokens: 8192 # MEMORY_DEFAULT_MAX_TRAJECTORY_TOKENS + default_max_question_tokens: 2048 # MEMORY_DEFAULT_MAX_QUESTION_TOKENS + + # LLM settings for the reflection modules. The Distiller summarizes a + # trajectory; the Cartographer curates the context memory. Both are compact + # summarize/curate tasks, so a cheaper tier than code review usually suffices. + # null inherits the top-level default_* value. + distiller: + model: null # MEMORY_DISTILLER_MODEL + reasoning_effort: null # MEMORY_DISTILLER_REASONING_EFFORT + temperature: null # MEMORY_DISTILLER_TEMPERATURE + max_tokens: null # MEMORY_DISTILLER_MAX_TOKENS + cartographer: + model: null # MEMORY_CARTOGRAPHER_MODEL + reasoning_effort: null # MEMORY_CARTOGRAPHER_REASONING_EFFORT + temperature: null # MEMORY_CARTOGRAPHER_TEMPERATURE + max_tokens: null # MEMORY_CARTOGRAPHER_MAX_TOKENS + + # ============================================================================ # SIGNATURES # ============================================================================ -# Each signature config supports: enabled, max_iters, model, max_context_size -# Set to null to use defaults + +# Each signature config supports: enabled, max_iters, model, reasoning_effort, +# temperature, max_tokens. Set to null to inherit the corresponding default_* value. + # # RECOMMENDED MODEL STRATEGY # ============================================================================ @@ -75,26 +160,47 @@ gitlab: # extraction (TwoStepAdapter). Needs accuracy but not deep reasoning. # Recommended: anthropic/claude-sonnet-4-5-20250929 or equivalent. # -# Cheap (summarization): Used for PR summary generation. Simple synthesis +# Cheap (summary): Used for PR summary generation. Simple synthesis # task. Recommended: anthropic/claude-haiku-4-5-20251001 or equivalent. # -# By default, all models fall back to default_model. Override extraction_model -# and summarization model for cost optimization: + # Mid-tier (memory.distiller / memory.cartographer): Memory reflection — + # summarizing a trajectory and curating the context memory. Requires nuance. + # Recommended: anthropic/claude-sonnet-4-5-20250929 or equivalent. +# +# By default, all models fall back to default_model. Override extraction_model, +# the summary model, and the reflection models for cost optimization: # # default_model: anthropic/claude-opus-4-6 # extraction_model: anthropic/claude-sonnet-4-5-20250929 # signatures: -# summarization: +# summary: # model: anthropic/claude-haiku-4-5-20251001 +# memory: +# distiller: +# model: anthropic/claude-sonnet-4-5-20250929 +# cartographer: +# model: anthropic/claude-sonnet-4-5-20250929 # ============================================================================ -# These apply to all signatures unless overridden per-signature + +# These apply to all signatures and reflection modules unless overridden default_model: anthropic/claude-opus-4-6 # DEFAULT_MODEL extraction_model: null # EXTRACTION_MODEL (falls back to default_model) -default_max_iters: 20 # DEFAULT_MAX_ITERS -default_max_context_size: 100000 # DEFAULT_MAX_CONTEXT_SIZE -default_max_reasoning_tokens: 6000 # DEFAULT_MAX_REASONING_TOKENS (limits LLM reasoning verbosity) -default_temperature: 0 # DEFAULT_TEMPERATURE (lower = more deterministic output) +default_max_iters: 10 # DEFAULT_MAX_ITERS +default_reasoning_effort: medium # DEFAULT_REASONING_EFFORT (minimal | low | medium | high) +default_temperature: 0.2 # DEFAULT_TEMPERATURE + +# Output token budget for a single completion. This is an OUTPUT ceiling, not a +# context window, and reasoning/thinking tokens are charged against it — so it +# must be well above the expected answer size whenever reasoning_effort is set. +# Leaving it unset is not an option: LiteLLM then silently falls back to its own +# 4096 default, which truncates responses mid-answer (DSPy warns +# "LM response was truncated ... max_tokens=None"). +# The value is clamped down to each model's real output ceiling before use, so a +# generous setting stays valid on smaller models. Models LiteLLM doesn't know +# (Ollama, custom endpoints) receive it unchanged. +default_max_tokens: 64000 # DEFAULT_MAX_TOKENS + # Global LLM reliability settings llm_retries: 3 # LLM_RETRIES (number of retries for LLM API calls) @@ -106,12 +212,15 @@ signatures: enabled: true # SUPPLY_CHAIN_ENABLED max_iters: null # SUPPLY_CHAIN_MAX_ITERS model: null # SUPPLY_CHAIN_MODEL (Haiku 4.5) - max_context_size: null # SUPPLY_CHAIN_MAX_CONTEXT_SIZE - max_reasoning_tokens: null # SUPPLY_CHAIN_MAX_REASONING_TOKENS + reasoning_effort: null # SUPPLY_CHAIN_REASONING_EFFORT temperature: null # SUPPLY_CHAIN_TEMPERATURE + max_tokens: null # SUPPLY_CHAIN_MAX_TOKENS scan_unchanged: false # SUPPLY_CHAIN_SCAN_UNCHANGED # When true: scans ALL artifacts (Dockerfiles, etc.) and manifests # When false (default): only scans artifacts/manifests that were modified in the MR + memory: + enabled: null # SUPPLY_CHAIN_MEMORY_ENABLED (null -> memory.default_enabled) + max_reflects: null # SUPPLY_CHAIN_MEMORY_MAX_REFLECTS # Code Reviewer signature (bugs, security, removed defensive code, code smells) # Unified code review: bugs, security vulnerabilities, and code smells in a single agent pass per scope @@ -119,35 +228,58 @@ signatures: enabled: true # CODE_REVIEW_ENABLED max_iters: null # CODE_REVIEW_MAX_ITERS model: null # CODE_REVIEW_MODEL - max_context_size: null # CODE_REVIEW_MAX_CONTEXT_SIZE - max_reasoning_tokens: null # CODE_REVIEW_MAX_REASONING_TOKENS + reasoning_effort: null # CODE_REVIEW_REASONING_EFFORT temperature: null # CODE_REVIEW_TEMPERATURE + max_tokens: null # CODE_REVIEW_MAX_TOKENS + memory: + enabled: null # CODE_REVIEW_MEMORY_ENABLED (null -> memory.default_enabled) + max_reflects: null # CODE_REVIEW_MEMORY_MAX_REFLECTS # Documentation Reviewer signature (compares patches against extracted documentation) # Note: doc extraction is now deterministic (no LLM) — see doc_extractor.py doc: enabled: true # DOC_ENABLED model: null # DOC_MODEL - max_context_size: null # DOC_MAX_CONTEXT_SIZE - max_reasoning_tokens: null # DOC_MAX_REASONING_TOKENS + reasoning_effort: null # DOC_REASONING_EFFORT temperature: null # DOC_TEMPERATURE + max_tokens: null # DOC_MAX_TOKENS + memory: + enabled: null # DOC_MEMORY_ENABLED (null -> memory.default_enabled) + max_reflects: null # DOC_MEMORY_MAX_REFLECTS # Scope Identifier signature scope: enabled: true # SCOPE_ENABLED - max_iters: null # SCOPE_MAX_ITERS + max_iters: 20 # SCOPE_MAX_ITERS model: null # SCOPE_MODEL - max_context_size: null # SCOPE_MAX_CONTEXT_SIZE - max_reasoning_tokens: null # SCOPE_MAX_REASONING_TOKENS + reasoning_effort: null # SCOPE_REASONING_EFFORT temperature: null # SCOPE_TEMPERATURE + max_tokens: null # SCOPE_MAX_TOKENS + memory: + enabled: null # SCOPE_MEMORY_ENABLED (null -> memory.default_enabled) + max_reflects: null # SCOPE_MEMORY_MAX_REFLECTS # Summarizer signature - summarization: - enabled: true # SUMMARIZATION_ENABLED - model: null # SUMMARIZATION_MODEL (falls back to default_model) - max_context_size: null # SUMMARIZATION_MAX_CONTEXT_SIZE - max_reasoning_tokens: null # SUMMARIZATION_MAX_REASONING_TOKENS - temperature: null # SUMMARIZATION_TEMPERATURE + summary: + enabled: true # SUMMARY_ENABLED + model: null # SUMMARY_MODEL (falls back to default_model) + reasoning_effort: null # SUMMARY_REASONING_EFFORT + temperature: null # SUMMARY_TEMPERATURE + max_tokens: null # SUMMARY_MAX_TOKENS + memory: + enabled: true # SUMMARY_MEMORY_ENABLED (null -> memory.default_enabled) + max_reflects: null # SUMMARY_MEMORY_MAX_REFLECTS + + # Auditor signature (quality assessment + recommendation after all reviews) + audit: + enabled: true # AUDIT_ENABLED + model: null # AUDIT_MODEL + reasoning_effort: null # AUDIT_REASONING_EFFORT + temperature: null # AUDIT_TEMPERATURE + max_tokens: null # AUDIT_MAX_TOKENS + memory: + enabled: null # AUDIT_MEMORY_ENABLED (null -> memory.default_enabled) + max_reflects: null # AUDIT_MEMORY_MAX_REFLECTS # ============================================================================ # OUTPUT diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..7bccbf7 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,153 @@ +[← Back to README](../README.md#documentation) + +# Architecture + +## Pipeline Overview + +CodeSpy's review pipeline follows a 4-step flow: + +1. **Scope Identifier** (ReAct + tools) — Identifies code scopes (frontend, backend, infra, microservice in monorepo, etc.) +2. **PR Summary** (ChainOfThought) — Generates 2-3 sentence PR summary +3. **Parallel Review Modules** — Supply Chain Auditor, Code Reviewer, and Doc Reviewer run simultaneously +4. **Auditor** (ChainOfThought) — Generates quality assessment + recommendation (APPROVE / REQUEST_CHANGES / NEEDS_DISCUSSION) + +## Architecture Diagram + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ codespy CLI │ +├─────────────────────────────────────────────────────────────────────┤ +│ review | review-local | review-uncommitted | serve │ +└──────────────────────────────┬──────────────────────────────────────┘ + │ +┌──────────────────────────────▼──────────────────────────────────────┐ +│ Git Platform Integration │ +│ GitHub / GitLab — fetch diff, changed files, commit messages │ +│ Auto-detects platform · Sparse checkout for full context │ +└──────────────────────────────┬──────────────────────────────────────┘ + │ +┌──────────────────────────────▼──────────────────────────────────────┐ +│ DSPy Review Pipeline │ +│ │ +│ ┌────────────────────────────────────────────────────────────┐ │ +│ │ 1. Scope Identifier (ReAct + tools) │ │ +│ │ Identifies code scopes: frontend, backend, infra, etc. │ │ +│ └──────────────────────────┬─────────────────────────────────┘ │ +│ │ │ +│ ┌──────────────────────────▼─────────────────────────────────┐ │ +│ │ 2. PR Summary (ChainOfThought) │ │ +│ │ Generates 2-3 sentence PR summary │ │ +│ └──────────────────────────┬─────────────────────────────────┘ │ +│ │ │ +│ ┌──────────────────────────▼─────────────────────────────────┐ │ +│ │ 3. Parallel Review Modules │ │ +│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │ │ +│ │ │ Supply Chain │ │ Code │ │ Doc │ │ │ +│ │ │ Auditor │ │ Reviewer │ │ Reviewer │ │ │ +│ │ │ (ReAct+tools)│ │ (ReAct+tools)│ │(ChainOfThought│ │ │ +│ │ └──────────────┘ └──────────────┘ └───────────────┘ │ │ +│ └──────────────────────────┬─────────────────────────────────┘ │ +│ │ │ +│ ┌──────────────────────────▼─────────────────────────────────┐ │ +│ │ 4. Auditor (ChainOfThought) │ │ +│ │ Quality assessment + recommendation │ │ +│ │ (APPROVE / REQUEST_CHANGES / NEEDS_DISCUSSION) │ │ +│ └────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐ │ +│ │ Hippocampus Memory (cross-cutting) │ │ +│ │ Episode persistence · Context memory · Distiller/ │ │ +│ │ Cartographer reflection · Topic-based organization │ │ +│ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘ │ +│ │ +│ Cost Tracker (tokens, calls, $) │ +└──────────────────────────────┬──────────────────────────────────────┘ + │ +┌──────────────────────────────▼──────────────────────────────────────┐ +│ Tools Layer │ +│ Filesystem · Git (GH+GL) · Web · Cyber/OSV │ +│ Parsers: Ripgrep (code search) · Tree-sitter (multi-lang AST) │ +└──────────────────────────────┬──────────────────────────────────────┘ + │ +┌──────────────────────────────▼──────────────────────────────────────┐ +│ LLM Backend (LiteLLM) │ +│ Bedrock | OpenAI | Anthropic | Gemini | Ollama | Any compatible │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +## DSPy Signatures + +| Signature | Config Key | Type | Description | +|-----------|------------|------|-------------| +| **ScopeRefinementSignature** | `scope` | ReAct | Identifies code scopes (frontend, backend, infra, microservice in monorepo, etc.) | +| **PRSummarySignature** | `summary` | ChainOfThought | Generates PR summary | +| **CodeReviewSignature** | `code_review` | ReAct | Detects verified bugs, security vulnerabilities, removed defensive code, and code smells | +| **DocReviewSignature** | `doc` | ChainOfThought | Detects stale or wrong documentation caused by code changes | +| **SupplyChainSecuritySignature** | `supply_chain` | ReAct | Analyzes artifacts (Dockerfiles) and dependencies for supply chain security | +| **AuditSignature** | `audit` | ChainOfThought | Generates quality assessment and recommendation | + +See [Configuration](configuration.md) for per-signature settings. + +### Context Window Overflow Resilience + +All signature modules are wrapped in `ContextSafe`, which provides transparent +fallback to `dspy.RLM` (Recursive Language Model) when the input exceeds the +model's context window. + +- **Pre-flight**: For models in litellm's database, estimates input tokens + before calling the module. If overflow is predicted, skips directly to RLM. +- **Try/catch**: For models not in litellm's database, catches the provider + error and retries with RLM automatically. + +RLM puts inputs into a sandboxed Python interpreter rather than the LLM prompt. +The model writes code to access and process variables (e.g., chunking large +patches via `llm_query()`), eliminating context window limits entirely. + +The composition order is: `Hippocampus(ContextSafe(Module))` — Hippocampus +handles memory, ContextSafe handles overflow, each with single responsibility. + +## Hippocampus Memory + +Episode-based memory that wraps DSPy agents with persistent context across reviews. Agents accumulate knowledge about a codebase scope over time — patterns, constants, parsing schemas, and reuse it in subsequent reviews of the same code area. + +See [Memory System](memory.md) for implementation details. + +## Tools Layer + +- **Filesystem**: `read_file`, `list_directory`, `find_files`, `search_text`, `get_file_summary` +- **Git**: GitHub + GitLab clients, sparse checkout +- **Parsers**: Ripgrep (code search) + Tree-sitter (multi-language AST) +- **Web**: Browser-based web search +- **Cyber/OSV**: Vulnerability scanning + +## Supported Languages + +Tree-sitter based parsing for context-aware analysis: + +| Language | Extensions | Features | +|----------|-----------|----------| +| Bash | `.sh`, `.bash` | Functions, commands | +| C/C++ | `.c`, `.cpp`, `.h`, `.hpp` | Functions, classes, structs | +| C# | `.cs` | Methods, classes, interfaces | +| Go | `.go` | Functions, structs, interfaces | +| Java | `.java` | Methods, classes, packages | +| JavaScript | `.js`, `.jsx` | Functions, classes, imports | +| Kotlin | `.kt` | Functions, classes, objects | +| Objective-C | `.m`, `.h` | Methods, interfaces, protocols | +| PHP | `.php` | Functions, classes, namespaces | +| Python | `.py` | Functions, classes, imports | +| Ruby | `.rb` | Methods, classes, modules | +| Rust | `.rs` | Functions, structs, traits, impl blocks | +| Swift | `.swift` | Functions, classes, structs | +| Terraform | `.tf` | Resources, data sources, modules, variables | +| TypeScript | `.ts`, `.tsx` | Functions, classes, interfaces | + +All languages are supported for security, bug, and documentation analysis. + +## LLM Backend + +LiteLLM routing to Bedrock, OpenAI, Anthropic, Gemini, Ollama, Azure, and any OpenAI-compatible endpoint. + +--- + +[← Back to README](../README.md#documentation) diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..b88f9fc --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,203 @@ +[← Back to README](../README.md#documentation) + +# Configuration + +Priority: CLI options > Environment Variables > YAML Config > Defaults + +## Setup + +```bash +cp .env.example .env +``` + +## Git Platform Tokens + +### GitHub Token + +Auto-discovered from: +- `GITHUB_TOKEN` or `GH_TOKEN` environment variables +- GitHub CLI (`gh auth token`) +- Git credential helper +- `~/.netrc` file + +Or create a token at https://github.com/settings/tokens with `repo` scope: +```bash +GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx +``` + +To disable auto-discovery: +```bash +GITHUB_AUTO_DISCOVER_TOKEN=false +``` + +### GitLab Token + +Auto-discovered from: +- `GITLAB_TOKEN` or `GITLAB_PRIVATE_TOKEN` environment variables +- GitLab CLI (`glab auth token`) +- Git credential helper +- `~/.netrc` file +- python-gitlab config files (`~/.python-gitlab.cfg`, `/etc/python-gitlab.cfg`) + +Or create a token at https://gitlab.com/-/user_settings/personal_access_tokens with `api` scope: +```bash +GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx +``` + +For self-hosted GitLab: +```bash +GITLAB_URL=https://gitlab.mycompany.com +GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx +``` + +To disable auto-discovery: +```bash +GITLAB_AUTO_DISCOVER_TOKEN=false +``` + +## LLM Provider + +codespy auto-discovers credentials for all providers: + +**Anthropic** (auto-discovers from `$ANTHROPIC_API_KEY`, `~/.config/anthropic/`, `~/.anthropic/`): +```bash +DEFAULT_MODEL=anthropic/claude-opus-4-6 +# Optional - set explicitly or let codespy auto-discover: +# ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxx +``` + +**AWS Bedrock** (auto-discovers from `~/.aws/credentials`, AWS CLI, env vars): +```bash +DEFAULT_MODEL=bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0 +AWS_REGION=us-east-1 +# Optional - uses ~/.aws/credentials by default, or set explicitly: +# AWS_ACCESS_KEY_ID=... +# AWS_SECRET_ACCESS_KEY=... +``` + +**OpenAI** (auto-discovers from `$OPENAI_API_KEY`, `~/.config/openai/`, `~/.openai/`): +```bash +DEFAULT_MODEL=openai/gpt-5 +# Optional - set explicitly or let codespy auto-discover: +# OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx +``` + +**Google Gemini** (auto-discovers from `$GEMINI_API_KEY`, `$GOOGLE_API_KEY`, gcloud ADC): +```bash +DEFAULT_MODEL=gemini/gemini-2.5-pro +# Optional - set explicitly or let codespy auto-discover: +# GEMINI_API_KEY=xxxxxxxxxxxxxxxxxxxx +``` + +**Local Ollama:** +```bash +DEFAULT_MODEL=ollama/llama3 +``` + +To disable auto-discovery for specific providers: +```bash +AUTO_DISCOVER_AWS=false +AUTO_DISCOVER_OPENAI=false +AUTO_DISCOVER_ANTHROPIC=false +AUTO_DISCOVER_GEMINI=false +``` + +## Model Settings + +| Setting | Env Var | Default | Description | +|---------|---------|---------|-------------| +| Model | `DEFAULT_MODEL` | `anthropic/claude-opus-4-6` | Primary model for all signatures | +| Reasoning effort | `DEFAULT_REASONING_EFFORT` | `medium` | Provider reasoning budget: `minimal`, `low`, `medium`, `high` | +| Max tokens | `DEFAULT_MAX_TOKENS` | `64000` | Output token budget per completion (reasoning tokens included) | +| Temperature | `DEFAULT_TEMPERATURE` | `0.2` | Default temperature for LLM calls | +| Max iterations | `DEFAULT_MAX_ITERS` | `10` | Maximum ReAct iterations for tool-using agents | +| Prompt caching | `ENABLE_PROMPT_CACHING` | `true` | Provider-side prompt caching (Anthropic, OpenAI, Bedrock) | + +## Recommended Model Strategy + +| Tier | Role | Env Var | Default | Recommended | +|------|------|---------|---------|-------------| +| Smart | Core analysis & reasoning | `DEFAULT_MODEL` | `anthropic/claude-opus-4-6` | Claude Opus / GPT-5 | +| Mid-tier | Field extraction | `EXTRACTION_MODEL` | Falls back to DEFAULT_MODEL | Claude Sonnet | +| Cheap | PR summary | `SUMMARY_MODEL` | Falls back to DEFAULT_MODEL | Claude Haiku | +| Mid-tier | Memory reflection | `MEMORY_DISTILLER_MODEL` / `MEMORY_CARTOGRAPHER_MODEL` | Falls back to DEFAULT_MODEL | Claude Sonnet | + +## Per-Signature Configuration + +Each signature supports env var overrides: `_` + +| Signature | Config Key | Available Settings | +|-----------|------------|-------------------| +| Scope Identifier | `scope` | ENABLED, MAX_ITERS, MODEL, REASONING_EFFORT, TEMPERATURE, MAX_TOKENS | +| PR Summary | `summary` | ENABLED, MODEL, REASONING_EFFORT, TEMPERATURE, MAX_TOKENS | +| Code Reviewer | `code_review` | ENABLED, MAX_ITERS, MODEL, REASONING_EFFORT, TEMPERATURE, MAX_TOKENS | +| Doc Reviewer | `doc` | ENABLED, MODEL, REASONING_EFFORT, TEMPERATURE, MAX_TOKENS | +| Supply Chain | `supply_chain` | ENABLED, MAX_ITERS, MODEL, REASONING_EFFORT, TEMPERATURE, MAX_TOKENS, SCAN_UNCHANGED | +| Auditor | `audit` | ENABLED, MODEL, REASONING_EFFORT, TEMPERATURE, MAX_TOKENS | + +Example: `CODE_REVIEW_MODEL=anthropic/claude-sonnet-4-5-20250929` + +## Advanced Configuration (YAML) + +For per-signature settings, use `codespy.yaml`. See [`codespy.yaml`](../codespy.yaml) for all available options including: +- LLM provider settings and auto-discovery +- Git platform configuration (GitHub/GitLab) +- Per-signature model and iteration overrides +- Output format and destination settings +- Directory exclusions + +Override YAML settings via environment variables using `_` separator: + +```bash +# Default settings +export DEFAULT_MODEL=anthropic/claude-opus-4-6 +export DEFAULT_MAX_ITERS=20 + +# Per-signature settings (use signature name, not module name) +export CODE_REVIEW_MODEL=anthropic/claude-sonnet-4-5-20250929 + +# Output settings +export OUTPUT_STDOUT=false +export OUTPUT_GIT=true +``` + +## Memory Configuration + +Brief overview: + +| Setting | Env Var | Default | Description | +|---------|---------|---------|-------------| +| Backend | `MEMORY_BACKEND` | `filesystem` | Storage: `filesystem` or `s3` | +| Root path | `MEMORY_ROOT` | `~/.cache/codespy/memory` | Filesystem storage location | +| Default enabled | `MEMORY_DEFAULT_ENABLED` | `false` | Enable memory globally | +| Max reflects | `MEMORY_DEFAULT_MAX_REFLECTS` | `0` | Reflection iterations (0 = once at end) | +| Context memory tokens | `MEMORY_DEFAULT_MAX_CONTEXT_MEMORY_TOKENS` | `8192` | Max tokens for persisted context memory | +| Item tokens | `MEMORY_DEFAULT_MAX_CONTEXT_ITEM_TOKENS` | `410` | Soft per-item token limit | +| Trajectory tokens | `MEMORY_DEFAULT_MAX_TRAJECTORY_TOKENS` | `8192` | Cap on trajectory fed to Distiller | +| Question tokens | `MEMORY_DEFAULT_MAX_QUESTION_TOKENS` | `2048` | Cap on serialized reflection inputs | + +See [Memory System](memory.md) for full memory configuration details. + +## Output Settings + +| Setting | Env Var | Default | Description | +|---------|---------|---------|-------------| +| Format | `OUTPUT_FORMAT` | `markdown` | `markdown` or `json` | +| Stdout | `OUTPUT_STDOUT` | `true` | Enable stdout output | +| Git | `OUTPUT_GIT` | `true` | Post review to GitHub/GitLab | +| Cache dir | `CACHE_DIR` | `~/.cache/codespy` | Cache directory path | + +## File Exclusions + +`EXCLUDED_DIRECTORIES` (JSON array in env) — Directories to skip during code review. Binary files, lock files, and minified files are always excluded automatically. + +Default excluded directories: +- Vendor/dependency: `vendor`, `node_modules`, `third_party`, `external`, `deps`, `_vendor`, `vendored` +- Build output: `dist`, `build`, `out`, `target` +- Package manager: `.bundle`, `Pods`, `Carthage`, `bower_components`, `jspm_packages` +- Version control: `.git`, `.svn`, `.hg` +- Cache: `__pycache__`, `.cache`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache` + +--- + +[← Back to README](../README.md#documentation) diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..14b0c20 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,82 @@ +[← Back to README](../README.md#documentation) + +# Development + +## Setup + +```bash +# Clone the repository +git clone https://github.com/khezen/codespy.git +cd codespy + +# Quick setup (creates .env and installs dependencies) +make setup + +# Or manually with Poetry: +poetry install # Install all dependencies including dev +poetry lock # Update lock file +``` + +## Make Targets + +```bash +make help # Show all available targets +make setup # Create .env and install dependencies +make lint # Run ruff linter +make format # Format code with ruff +make typecheck # Run mypy type checker +make test # Run pytest tests +make build # Build package with Poetry +make clean # Clean build artifacts +``` + +## Running Directly + +```bash +# Run codespy via Poetry +poetry run codespy review https://github.com/owner/repo/pull/123 + +# Run linters directly +poetry run ruff check src/ +poetry run mypy src/ +``` + +## Project Structure + +``` +src/codespy/ +├── __init__.py +├── cli.py # Main CLI entrypoint +├── cli_local.py # Local review commands +├── cli_remote.py # Remote PR/MR review commands +├── cli_mcp_server.py # MCP server command +├── config.py # Main configuration +├── config_dspy.py # DSPy configuration +├── config_git.py # Git platform configuration +├── config_io.py # I/O configuration +├── config_llm.py # LLM provider configuration +├── config_memory.py # Memory system configuration +├── agents/ # DSPy agents and pipeline +│ ├── context_safe.py # Context window overflow resilience (RLM fallback) +│ ├── cost_tracker.py # Token/cost tracking +│ ├── dspy_config.py # DSPy runtime config +│ ├── memory/ # Hippocampus memory system +│ │ └── hippocampus/ # Episode persistence, context memory, budget +│ └── reviewer/ # Review pipeline +│ ├── models.py # Review data models +│ ├── reviewer.py # Main review orchestrator +│ ├── server.py # MCP server implementation +│ ├── modules/ # Pipeline stages (scope_resolver, summarizer, code_reviewer, doc_reviewer, supply_chain_auditor, auditor) +│ └── reporters/ # Output reporters (git comments, stdout) +└── tools/ # Agent tools + ├── git/ # GitHub/GitLab clients, local diff, patch utils + ├── cyber/osv/ # OSV vulnerability scanning + ├── parsers/ # Ripgrep + Tree-sitter + ├── storage/ # Filesystem + S3 + ├── web/ # Web search client + └── mcp_utils.py # MCP tool utilities +``` + +--- + +[← Back to README](../README.md#documentation) diff --git a/docs/memory.md b/docs/memory.md new file mode 100644 index 0000000..71bdfbb --- /dev/null +++ b/docs/memory.md @@ -0,0 +1,152 @@ +[← Back to README](../README.md#documentation) + +# Hippocampus Memory System + +## Overview + +Episode-based memory that wraps DSPy agents with persistent context across reviews. +Agents accumulate knowledge about a codebase scope over time — patterns, constants, +parsing schemas — and reuse it in subsequent reviews of the same code area. + +## Concepts + +### Topics + +- Every scope gets a `topic_id` derived from `make_topic_id(repo_slug, subroot)` +- Topics organize memory by code area so knowledge doesn't bleed between scopes +- `compute_common_ancestor_topic_id()` finds shared parent for cross-scope queries + +### Episodes + +- An Episode captures one agent's run: task, context_memory, mutations, timestamp +- Stored as JSON at: `/global/episodic///codespy--.json` +- `find_latest_episode()` loads the most recent episode by `modified_at` for a given path prefix + +### Context Memory + +Five sections (from general to specific): + +1. **`context_roadmap`** — High-level codebase structure and navigation hints +2. **`context_understanding`** — Domain knowledge and design patterns observed +3. **`domain_constants`** — Exact values, URLs, identifiers that repeat across reviews +4. **`parsing_schema`** — File format conventions, naming patterns, structural rules +5. **`reusable_results`** — Computed facts reusable in future reviews + +Each section contains Items with tags (general, scope-specific) and text content. + +## Reflection Pipeline + +After each agent run (at `end_episode()`): + +1. **Distiller** — Analyzes the agent's trajectory (head 60% + tail 40%, capped at `max_trajectory_tokens`) and proposes `CacheCandidate` items for context memory +2. **Cartographer** — Takes candidates + current context memory, decides operations: + - `ADD` — Insert new item + - `REPLACE` — Update existing item with new knowledge + - `DELETE` — Remove outdated/irrelevant item +3. **Eviction** — If memory exceeds `max_context_memory_tokens`, oldest general items are evicted first + +Reflection iterates `max_reflects` times (0 = reflect once at end_episode). + +## Token Budgets + +| Budget | Env Var | Default | Purpose | +|--------|---------|---------|---------| +| Context memory | `MEMORY_DEFAULT_MAX_CONTEXT_MEMORY_TOKENS` | 8192 | Ceiling on persisted ContextMemory (re-sent every iteration) | +| Item | `MEMORY_DEFAULT_MAX_CONTEXT_ITEM_TOKENS` | 410 | Soft per-item limit (expressed to LLM, not truncated) | +| Trajectory | `MEMORY_DEFAULT_MAX_TRAJECTORY_TOKENS` | 8192 | Head+tail cap on trajectory fed to Distiller | +| Question | `MEMORY_DEFAULT_MAX_QUESTION_TOKENS` | 2048 | Cap on serialized inputs as reflection question | + +Item capacity ≈ context_memory_tokens / item_tokens (8192/410 ≈ 19 items) + +## Configuration + +### Global Settings + +| Env Var | YAML Path | Default | Description | +|---------|-----------|---------|-------------| +| `MEMORY_BACKEND` | `memory.backend` | `filesystem` | Storage backend: `filesystem` or `s3` | +| `MEMORY_ROOT` | `memory.root` | `~/.cache/codespy/memory` | Filesystem storage path | +| `MEMORY_S3_BUCKET` | `memory.s3_bucket` | — | S3 bucket name | +| `MEMORY_S3_REGION` | `memory.s3_region` | (aws_region) | S3 region | +| `MEMORY_S3_ENDPOINT_URL` | `memory.s3_endpoint_url` | — | MinIO/S3-compatible endpoint | +| `MEMORY_DEFAULT_ENABLED` | `memory.default_enabled` | `false` | Enable memory globally | +| `MEMORY_DEFAULT_MAX_REFLECTS` | `memory.default_max_reflects` | `0` | Reflection iterations (0 = once at end) | + +### Reflection Module LLM Overrides + +| Module | Env Var Pattern | YAML Path | +|--------|----------------|-----------| +| Distiller | `MEMORY_DISTILLER_{MODEL,REASONING_EFFORT,TEMPERATURE,MAX_TOKENS}` | `memory.distiller.*` | +| Cartographer | `MEMORY_CARTOGRAPHER_{MODEL,REASONING_EFFORT,TEMPERATURE,MAX_TOKENS}` | `memory.cartographer.*` | + +### Per-Signature Memory Overrides + +Each signature's `memory:` block in YAML (or `_MEMORY_*` env vars): + +| Setting | Env Var Suffix | Description | +|---------|---------------|-------------| +| enabled | `_MEMORY_ENABLED` | Enable/disable memory for this signature | +| max_reflects | `_MEMORY_MAX_REFLECTS` | Override reflection count | + +Example: `CODE_REVIEW_MEMORY_ENABLED=true` or `SUMMARY_MEMORY_MAX_REFLECTS=2` + +See [Configuration](configuration.md#recommended-model-strategy) for recommended reflection models. + +## Quick Start + +Enable memory for code review: +```bash +MEMORY_DEFAULT_ENABLED=true +# Or per-signature: +CODE_REVIEW_MEMORY_ENABLED=true +SUMMARY_MEMORY_ENABLED=true +``` + +Recommended mid-tier reflection model: +```bash +MEMORY_DISTILLER_MODEL=anthropic/claude-sonnet-4-5-20250929 +MEMORY_CARTOGRAPHER_MODEL=anthropic/claude-sonnet-4-5-20250929 +``` + +### GitHub Action + +Enable memory with S3 persistence: +```yaml +- name: Run CodeSpy Review + uses: khezen/codespy@v1 + with: + model: 'anthropic/claude-opus-4-6' + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} + # AWS credentials (required for S3 memory backend) + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: 'us-east-1' + # Memory + memory-enabled: 'true' + memory-backend: 's3' + memory-s3-bucket: 'my-codespy-memory' + memory-distiller-model: 'anthropic/claude-haiku-4-5-20251001' + memory-cartographer-model: 'anthropic/claude-haiku-4-5-20251001' +``` + +Enable only for code review (per-signature override): +```yaml +- name: Run CodeSpy Review + uses: khezen/codespy@v1 + with: + model: 'anthropic/claude-opus-4-6' + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + memory-backend: 's3' + memory-s3-bucket: 'my-codespy-memory' + code-review-memory-enabled: 'true' + memory-distiller-model: 'anthropic/claude-haiku-4-5-20251001' + memory-cartographer-model: 'anthropic/claude-haiku-4-5-20251001' +``` + +> **Note:** The `filesystem` backend is ephemeral in the GitHub Action (Docker container is removed after each run). Use `s3` for persistent memory across reviews. + +--- + +[← Back to README](../README.md#documentation) diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..f4d292b --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,285 @@ +[← Back to README](../README.md#documentation) + +# Usage Guide + +## Command Line + +```bash +# Review GitHub Pull Request +codespy review https://github.com/owner/repo/pull/123 + +# Review GitLab Merge Request +codespy review https://gitlab.com/group/project/-/merge_requests/123 + +# GitLab with nested groups +codespy review https://gitlab.com/group/subgroup/project/-/merge_requests/123 + +# Self-hosted GitLab +codespy review https://gitlab.mycompany.com/team/project/-/merge_requests/123 + +# Output as JSON +codespy review https://github.com/owner/repo/pull/123 --output json + +# Use a specific model +codespy review https://github.com/owner/repo/pull/123 --model anthropic/claude-opus-4-6 + +# Use a custom config file +codespy review https://github.com/owner/repo/pull/123 --config path/to/config.yaml +codespy review https://github.com/owner/repo/pull/123 -f staging.yaml + +# Disable stdout output (useful with --git-comment) +codespy review https://github.com/owner/repo/pull/123 --no-stdout + +# Post review as GitHub/GitLab comment +codespy review https://github.com/owner/repo/pull/123 --git-comment + +# Combine: only post to Git platform, no stdout +codespy review https://github.com/owner/repo/pull/123 --no-stdout --git-comment + +# Show current configuration +codespy config + +# Show configuration from a specific file +codespy config --config path/to/config.yaml + +# Show version +codespy --version + +# Review local git changes (no GitHub/GitLab needed) +codespy review-local # Review current dir vs main +codespy review-local /path/to/repo # Review specific repo +codespy review-local --base develop # Compare against develop +codespy review-local --base origin/main # Compare against origin/main +codespy review-local --base HEAD~5 # Compare against 5 commits back +codespy review-local --model anthropic/claude-sonnet-4-5-20250929 +codespy review-local --output json --config staging.yaml + +# Review uncommitted changes (staged + unstaged) +codespy review-uncommitted # Review current dir +codespy review-uncommitted /path/to/repo +codespy review-uncommitted --output json +codespy review-uncommitted --model anthropic/claude-sonnet-4-5-20250929 +codespy review-uncommitted --config staging.yaml +``` + +## IDE Integration (MCP Server) + +CodeSpy can run as an MCP (Model Context Protocol) server for integration with AI coding assistants like Cline, enabling code reviews directly from your editor without leaving your workflow. + +```bash +# Start the MCP server +codespy serve + +# Use a custom config file +codespy serve --config path/to/config.yaml +``` + +**Configure your IDE** (example for Cline in VS Code): + +Add to `cline_mcp_settings.json`: +```json +{ + "mcpServers": { + "codespy-reviewer": { + "command": "codespy", + "args": ["serve"], + "env": { + "DEFAULT_MODEL": "anthropic/claude-opus-4-6", + "ANTHROPIC_API_KEY": "your-key-here" + } + } + } +} +``` + +Or for AWS Bedrock: +```json +{ + "mcpServers": { + "codespy-reviewer": { + "command": "codespy", + "args": ["serve"], + "env": { + "DEFAULT_MODEL": "bedrock/us.anthropic.claude-opus-4-6-v1", + "AWS_REGION": "us-east-1", + "AWS_ACCESS_KEY_ID": "your-access-key", + "AWS_SECRET_ACCESS_KEY": "your-secret-key" + } + } + } +} +``` + +**Available MCP Tools:** +- `review_local_changes(repo_path, base_ref)` — Review branch changes vs base (e.g., vs `main`) +- `review_uncommitted(repo_path)` — Review staged + unstaged working tree changes +- `review_pr(pr_url)` — Review a GitHub PR or GitLab MR by URL + +Then ask your AI assistant: *"Review my local changes"* or *"Review uncommitted work in /path/to/repo"* + +## Docker + +```bash +# With docker run (using GHCR image) +docker run --rm \ + -e GITHUB_TOKEN=$GITHUB_TOKEN \ + -e DEFAULT_MODEL=anthropic/claude-opus-4-6 \ + -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ + ghcr.io/khezen/codespy:latest review https://github.com/owner/repo/pull/123 + +# Or use a specific version +docker run --rm \ + -e GITHUB_TOKEN=$GITHUB_TOKEN \ + -e DEFAULT_MODEL=anthropic/claude-opus-4-6 \ + -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ + ghcr.io/khezen/codespy:0.2.1 review https://github.com/owner/repo/pull/123 +``` + +## GitHub Action + +Add CodeSpy to your repository for automatic PR reviews: + +**Trigger on `/codespy review` comment:** + +```yaml +# .github/workflows/codespy-review.yml +name: CodeSpy Code Review + +on: + issue_comment: + types: [created] + +jobs: + review: + # Only run on PR comments containing '/codespy review' + if: | + github.event.issue.pull_request && + contains(github.event.comment.body, '/codespy review') + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - name: Run CodeSpy Review + uses: khezen/codespy@v1 + with: + model: 'anthropic/claude-opus-4-6' + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} +``` + +**Trigger automatically on every PR:** + +```yaml +# .github/workflows/codespy-review.yml +name: CodeSpy Code Review + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + review: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - name: Run CodeSpy Review + uses: khezen/codespy@v1 + with: + model: 'anthropic/claude-opus-4-6' + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} +``` + +See [`../.github/workflows/codespy-review.yml.example`](../.github/workflows/codespy-review.yml.example) for more examples. + +## Output + +### Markdown (default) + +```markdown +# Code Review: Add user authentication + +**PR:** [owner/repo#123](https://github.com/owner/repo/pull/123) +**Reviewed at:** 2024-01-15 10:30 UTC +**Model:** anthropic/claude-opus-4-6 + +## Summary + +This PR implements user authentication with JWT tokens... + +## Statistics + +- **Total Issues:** 3 +- **Critical:** 1 +- **Security:** 1 +- **Bugs:** 1 +- **Documentation:** 1 + +## Issues + +### 🔴 Critical (1) + +#### SQL Injection Vulnerability + +**Location:** `src/auth/login.py:45` +**Category:** security + +The user input is directly interpolated into the SQL query... + +**Code:** +query = f"SELECT * FROM users WHERE username = '{username}'" + +**Suggestion:** +Use parameterized queries instead... + +**Reference:** [CWE-89](https://cwe.mitre.org/data/definitions/89.html) +``` + +### GitHub/GitLab Review Comments + +CodeSpy can post reviews directly to GitHub PRs or GitLab MRs as native review comments with inline annotations. + +**Enable via CLI:** +```bash +# GitHub +codespy review https://github.com/owner/repo/pull/123 --git-comment + +# GitLab +codespy review https://gitlab.com/group/project/-/merge_requests/123 --git-comment + +# Combine: only post to platform, no stdout +codespy review https://github.com/owner/repo/pull/123 --no-stdout --git-comment +``` + +**Enable via configuration:** +```bash +# Environment variable +export OUTPUT_GIT=true + +# Or in codespy.yaml +output_git: true +``` + +**Features:** + +- 🎯 **Inline Comments** - Issues are posted as review comments on the exact lines where they occur +- 📏 **Multi-line Support** - Issues spanning multiple lines are annotated with start/end line ranges +- 🔴🟠🟡🔵 **Severity Indicators** - Visual emoji markers for Critical, High, Medium, Low severity +- 📦 **Collapsible Sections** - Organized review body with expandable details: + - 📋 Summary of changes + - 🎯 Quality Assessment + - 📊 Statistics table + - 💰 Cost breakdown per signature + - 💡 Recommendation +- 🔗 **CWE References** - Security issues link directly to MITRE CWE database + +--- + +See [Configuration](configuration.md) for all available settings. + +--- + +[← Back to README](../README.md#documentation) diff --git a/poetry.lock b/poetry.lock index 2dde032..0f21fb5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,142 +2,141 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.7.1" description = "Happy Eyeballs for asyncio" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472"}, + {file = "aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d"}, ] [[package]] name = "aiohttp" -version = "3.13.3" +version = "3.14.3" description = "Async http client/server framework (asyncio)" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "aiohttp-3.13.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a372fd5afd301b3a89582817fdcdb6c34124787c70dbcc616f259013e7eef7"}, - {file = "aiohttp-3.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:147e422fd1223005c22b4fe080f5d93ced44460f5f9c105406b753612b587821"}, - {file = "aiohttp-3.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859bd3f2156e81dd01432f5849fc73e2243d4a487c4fd26609b1299534ee1845"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dca68018bf48c251ba17c72ed479f4dafe9dbd5a73707ad8d28a38d11f3d42af"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fee0c6bc7db1de362252affec009707a17478a00ec69f797d23ca256e36d5940"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c048058117fd649334d81b4b526e94bde3ccaddb20463a815ced6ecbb7d11160"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:215a685b6fbbfcf71dfe96e3eba7a6f58f10da1dfdf4889c7dd856abe430dca7"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2c184bb1fe2cbd2cefba613e9db29a5ab559323f994b6737e370d3da0ac455"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75ca857eba4e20ce9f546cd59c7007b33906a4cd48f2ff6ccf1ccfc3b646f279"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81e97251d9298386c2b7dbeb490d3d1badbdc69107fb8c9299dd04eb39bddc0e"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0e2d366af265797506f0283487223146af57815b388623f0357ef7eac9b209d"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4e239d501f73d6db1522599e14b9b321a7e3b1de66ce33d53a765d975e9f4808"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0db318f7a6f065d84cb1e02662c526294450b314a02bd9e2a8e67f0d8564ce40"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:bfc1cc2fe31a6026a8a88e4ecfb98d7f6b1fec150cfd708adbfd1d2f42257c29"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af71fff7bac6bb7508956696dce8f6eec2bbb045eceb40343944b1ae62b5ef11"}, - {file = "aiohttp-3.13.3-cp310-cp310-win32.whl", hash = "sha256:37da61e244d1749798c151421602884db5270faf479cf0ef03af0ff68954c9dd"}, - {file = "aiohttp-3.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:7e63f210bc1b57ef699035f2b4b6d9ce096b5914414a49b0997c839b2bd2223c"}, - {file = "aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b"}, - {file = "aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64"}, - {file = "aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29"}, - {file = "aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239"}, - {file = "aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f"}, - {file = "aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c"}, - {file = "aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168"}, - {file = "aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a"}, - {file = "aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046"}, - {file = "aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57"}, - {file = "aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c"}, - {file = "aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9"}, - {file = "aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591"}, - {file = "aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf"}, - {file = "aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e"}, - {file = "aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808"}, - {file = "aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415"}, - {file = "aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43"}, - {file = "aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1"}, - {file = "aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984"}, - {file = "aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c"}, - {file = "aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592"}, - {file = "aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa"}, - {file = "aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767"}, - {file = "aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344"}, - {file = "aiohttp-3.13.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31a83ea4aead760dfcb6962efb1d861db48c34379f2ff72db9ddddd4cda9ea2e"}, - {file = "aiohttp-3.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:988a8c5e317544fdf0d39871559e67b6341065b87fceac641108c2096d5506b7"}, - {file = "aiohttp-3.13.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b174f267b5cfb9a7dba9ee6859cecd234e9a681841eb85068059bc867fb8f02"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:947c26539750deeaee933b000fb6517cc770bbd064bad6033f1cff4803881e43"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9ebf57d09e131f5323464bd347135a88622d1c0976e88ce15b670e7ad57e4bd6"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4ae5b5a0e1926e504c81c5b84353e7a5516d8778fbbff00429fe7b05bb25cbce"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ba0eea45eb5cc3172dbfc497c066f19c41bac70963ea1a67d51fc92e4cf9a80"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bae5c2ed2eae26cc382020edad80d01f36cb8e746da40b292e68fec40421dc6a"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a60e60746623925eab7d25823329941aee7242d559baa119ca2b253c88a7bd6"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e50a2e1404f063427c9d027378472316201a2290959a295169bcf25992d04558"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:9a9dc347e5a3dc7dfdbc1f82da0ef29e388ddb2ed281bfce9dd8248a313e62b7"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b46020d11d23fe16551466c77823df9cc2f2c1e63cc965daf67fa5eec6ca1877"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:69c56fbc1993fa17043e24a546959c0178fe2b5782405ad4559e6c13975c15e3"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b99281b0704c103d4e11e72a76f1b543d4946fea7dd10767e7e1b5f00d4e5704"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:40c5e40ecc29ba010656c18052b877a1c28f84344825efa106705e835c28530f"}, - {file = "aiohttp-3.13.3-cp39-cp39-win32.whl", hash = "sha256:56339a36b9f1fc708260c76c87e593e2afb30d26de9ae1eb445b5e051b98a7a1"}, - {file = "aiohttp-3.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:c6b8568a3bb5819a0ad087f16d40e5a3fb6099f39ea1d5625a3edc1e923fc538"}, - {file = "aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88"}, + {file = "aiohttp-3.14.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b"}, + {file = "aiohttp-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a"}, + {file = "aiohttp-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32"}, + {file = "aiohttp-3.14.3-cp310-cp310-win32.whl", hash = "sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e"}, + {file = "aiohttp-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c"}, + {file = "aiohttp-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb"}, + {file = "aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3"}, + {file = "aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a"}, + {file = "aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7"}, + {file = "aiohttp-3.14.3-cp311-cp311-win32.whl", hash = "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa"}, + {file = "aiohttp-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d"}, + {file = "aiohttp-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39"}, + {file = "aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5"}, + {file = "aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228"}, + {file = "aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19"}, + {file = "aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559"}, + {file = "aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a"}, + {file = "aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c"}, + {file = "aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86"}, + {file = "aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627"}, + {file = "aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82"}, + {file = "aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c"}, + {file = "aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f"}, + {file = "aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80"}, + {file = "aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0"}, + {file = "aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71"}, + {file = "aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0"}, + {file = "aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883"}, + {file = "aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2"}, + {file = "aiohttp-3.14.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062"}, + {file = "aiohttp-3.14.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6"}, + {file = "aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919"}, + {file = "aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7"}, + {file = "aiohttp-3.14.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0"}, + {file = "aiohttp-3.14.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924"}, + {file = "aiohttp-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646"}, + {file = "aiohttp-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf"}, + {file = "aiohttp-3.14.3-cp314-cp314-win32.whl", hash = "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da"}, + {file = "aiohttp-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100"}, + {file = "aiohttp-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc"}, + {file = "aiohttp-3.14.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b"}, + {file = "aiohttp-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0"}, + {file = "aiohttp-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb"}, + {file = "aiohttp-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963"}, + {file = "aiohttp-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b"}, + {file = "aiohttp-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7"}, + {file = "aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc"}, ] [package.dependencies] @@ -147,6 +146,7 @@ attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" propcache = ">=0.2.0" +typing_extensions = {version = ">=4.4", markers = "python_version < \"3.13\""} yarl = ">=1.17.0,<2.0" [package.extras] @@ -168,44 +168,36 @@ frozenlist = ">=1.1.0" typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} [[package]] -name = "alembic" -version = "1.18.3" -description = "A database migration tool for SQLAlchemy." +name = "annotated-doc" +version = "0.0.5" +description = "Document parameters, class attributes, return types, and variables inline, with Annotated." optional = false -python-versions = ">=3.10" +python-versions = ">=3.9" files = [ - {file = "alembic-1.18.3-py3-none-any.whl", hash = "sha256:12a0359bfc068a4ecbb9b3b02cf77856033abfdb59e4a5aca08b7eacd7b74ddd"}, - {file = "alembic-1.18.3.tar.gz", hash = "sha256:1212aa3778626f2b0f0aa6dd4e99a5f99b94bd25a0c1ac0bba3be65e081e50b0"}, + {file = "annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101"}, + {file = "annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb"}, ] -[package.dependencies] -Mako = "*" -SQLAlchemy = ">=1.4.23" -typing-extensions = ">=4.12" - -[package.extras] -tz = ["tzdata"] - [[package]] name = "annotated-types" -version = "0.7.0" +version = "0.8.0" description = "Reusable constraint types to use with typing.Annotated" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, + {file = "annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0"}, + {file = "annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7"}, ] [[package]] name = "anyio" -version = "4.12.1" +version = "4.14.2" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c"}, - {file = "anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703"}, + {file = "anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494"}, + {file = "anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f"}, ] [package.dependencies] @@ -213,42 +205,94 @@ idna = ">=2.8" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -trio = ["trio (>=0.31.0)", "trio (>=0.32.0)"] +trio = ["trio (>=0.32.0)"] [[package]] -name = "asyncer" -version = "0.0.8" -description = "Asyncer, async and await, focused on developer experience." +name = "ast-serialize" +version = "0.8.0" +description = "Python bindings for mypy AST serialization" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "asyncer-0.0.8-py3-none-any.whl", hash = "sha256:5920d48fc99c8f8f0f1576e1882f5022885589c5fcbc46ce4224ec3e53776eeb"}, - {file = "asyncer-0.0.8.tar.gz", hash = "sha256:a589d980f57e20efb07ed91d0dbe67f1d2fd343e7142c66d3a099f05c620739c"}, + {file = "ast_serialize-0.8.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3d822605fa7bb326ef868d25fafced7fc660fa46d9b90c02ea86d5e2f5d325f7"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:2efa40b068197d5efb62655b43baadb842ed71c4958cccd3e8b86a35726f0119"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:db1b957291bca08c7e72f43a12357b2948e20775d970e3fc3dac0aa3160ab725"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdc0d5b18ff8fb364e87923e47c0a91d0d69dbcaeaa274591f7fd26892cc3a3a"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9da7330f3e235bf7da89b8d39205c6350fc0c08a85379743f2df9fff87d6d980"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3186969ee66a9863b00acc6523ace44c56974eecb348a7ea4b228d9f0b80e19"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40a57b73731be45da4fa41430c4d5dc94a24b3a4faba7b9e069978c0402064ea"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b9da3ef807eda752502446dfecea3b381c4900b7e27a5d5f4f899eb39951"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:293cc1c5bfa741f8e3fbe8175b9c07beee487c9a6fdbb25a5acad9f1df2d30a9"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0910c3442a75216dde0f102d854ba2aaa71d2482e0ee213630b9bf29584fba3"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43dd6d596879bb1cb8a12cc9dae7bb10090a39a35883026c24f82488a195619a"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8c9d537f59e936392cfd3597789d1390304dd659efc3c486ce7f40fb6b8a9f53"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f0190a33d7f97c65e9069f7a7f40499eea6b5cbe260c558378109caf20ce934b"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:77308ae6c5cf5264cc0f01a7c556ec77a9e68eb1f61b093534d698139fdc3b14"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8d53a23f27e1ed3a36b2d26fd2a1a6228c8e85a1ed62ff7cdb44bd610769f20a"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa5e7cb08f96fed9121f77b224151e41caf88feab9d652bb46c78202b6fbeda"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:fa70ed4dea0bb18b30a1789c77baa701d0ef30c474f2ccabdea61e25623a8827"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d8b3c8eee4c1baef9d4e84d2a59a805501617127be42615cb48970b15b0892b6"}, + {file = "ast_serialize-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:ac4f0a83c55a9b782f79ad55a5247b7db123c1db405959791c2ef886e9710c9f"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:86b8a1e6d90467345356098b040150e82fbc26d24a7a202224b13dc1f6264ca0"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:39e92ff8e8cb45947fe9007174b2950e1fb098e6abd00266a13cd3bcf6675068"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85d8d18db5b2dfcb3b7e38a4d600ca35504c0ed8a6f75cd1c811e4ffe248a15"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9830ff7e764f74d9eefb01170c61a9f0fd2c027dac5fcb72e064decd57d56371"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6479d9722a4cd21b578f5478074c41e6169f04811996ec881655560f703a5bba"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a63bed264e818cd83eec11feed0f50aa162542b91132ef58afebc857182763a5"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d187197d234aa45d6cfa2b096be5f666e8cc2e7eb3722d0ab8926293cf5720c"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:2d39a56282cfcc0d8eeea37267c754be59c98d48505c23b1dae5c6011f3813dd"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7cc5f10386994c0f4844f1e6d6a97127e9b478660eb6dec2b257644f0acab64"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6102f2f985c2e542be85cd857678ec9356fefa792b93cadfadd31139f5696f27"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:3a8660fe66667b76a6e9dccd1d33e66b229fde3b308db991c041609226c005b6"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:e7266307e5fba39836edb79def8608887af48820508bff3c5f2941e1e04d1534"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca7e6fd1ad845d1cc649dc2ecd499db2f8f46af5bf8da7b70dd858774cc038b"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:2880350b13d3eae69a0d70bc1fb6c9bfaca4dbd0e20ba8cd1aa483080b56ff06"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:ab0f9a59f7d63d0d441b56b9a818b273705264352d5115cfee12e940e816d958"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-win32.whl", hash = "sha256:0485a25ef519c62e749ee3c1ad8070e591b380d67226349eb5a70b228dc1ac4a"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:bd84d60bca7079e741be4ac5dbe237751a59d7f6f9f0126b11880d63822cbe16"}, + {file = "ast_serialize-0.8.0-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:057769b5921336eb2d9124f2a731b42ed05ffdac559b840dbdf6f3937cf153dc"}, + {file = "ast_serialize-0.8.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:a02cbed7d8bfdcdee88edaac12bd50d53d9953aaa2e1852ef078625be5f1c0b5"}, + {file = "ast_serialize-0.8.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e1bd223df0f6c96b396975fa604cb33bce53d9b4a0185490be4c4a289f7c9c87"}, + {file = "ast_serialize-0.8.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ddd3b61f45c132da66c5476b281891e08c1fd87fbdabe8a6973e1622efc85f06"}, + {file = "ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f9caa63fad8241257ae401b5ff0a64026c6adb36b8e86cbe8782d9ea505daf6"}, + {file = "ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3926fa117b5e65019853a2969966d11c7175af377a3425991f3fe73784412405"}, + {file = "ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:485f1113af805e9e170b95ef993ca3fbd4f89c04bab25c58b4fc632d854801ab"}, + {file = "ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccebbed24f1281062d5852353c72c47502955926cfcb8345ffb3a44d87ff3d3"}, + {file = "ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:252f883290d1cdb728eb7fe1d9a7221b88af5a329aae0bc91ddee4dafb820331"}, + {file = "ast_serialize-0.8.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:96abc072ad29db8d02194afd47d68987322622787daceae82398d7b69f3ba2e6"}, + {file = "ast_serialize-0.8.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9118ad3e369727060b2696fc4078f250ecffca4248ba87f537f55cea9f9dce06"}, + {file = "ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f359df4bd921918af8bebd142a376c77511d7151cc8ba852760b587b5a4a54f3"}, + {file = "ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:e94f9121d13fa36cbf21314783c77d05ae3a0868decd18cf5233fdcc6de49ac8"}, + {file = "ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:54f95b486018d262bcb387a9afd96f0da74508b442762b80c769454a6fbb3ee3"}, + {file = "ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c38b915511e32bc718c49dbce98ff9af36bac0ad6a604f58000cd5e3aecdba7"}, + {file = "ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:9a2ef9cf12f2de4f1028c42c1dd7d775255e0fb3e5bb48896c97e35ef52366fe"}, + {file = "ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f18048fe9f6dd266bd577cdec48bdcecb74faaa01fe941324435483b013ed2a"}, + {file = "ast_serialize-0.8.0-cp39-abi3-win32.whl", hash = "sha256:31883542dd6c94d178f5db3d32fbd69c5eb88b3a7c018e7ac8cc0c45195ddbed"}, + {file = "ast_serialize-0.8.0-cp39-abi3-win_amd64.whl", hash = "sha256:861794565b06337005c1447ef23103a3d5a627d08bdc827870d00d0b28ef5f51"}, + {file = "ast_serialize-0.8.0-cp39-abi3-win_arm64.whl", hash = "sha256:b2a5978662fd4db463dfb4b974d2b10ac6430b98f5333aabc7051909df3561d0"}, + {file = "ast_serialize-0.8.0.tar.gz", hash = "sha256:6c37c43e4004dfb42d321ddedc569dc17ff4259296f3af577c9ea46a809bc010"}, ] -[package.dependencies] -anyio = ">=3.4.0,<5.0" - [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" files = [ - {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, - {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, + {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, + {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, ] [[package]] name = "beautifulsoup4" -version = "4.14.3" +version = "4.15.0" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" files = [ - {file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"}, - {file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"}, + {file = "beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9"}, + {file = "beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7"}, ] [package.dependencies] @@ -264,41 +308,41 @@ lxml = ["lxml"] [[package]] name = "boto3" -version = "1.42.40" +version = "1.43.72" description = "The AWS SDK for Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "boto3-1.42.40-py3-none-any.whl", hash = "sha256:91d776b8b68006c1aca204d384be191883c2a36443f4a90561165986dae17b74"}, - {file = "boto3-1.42.40.tar.gz", hash = "sha256:e9e08059ae1bd47de411d361e9bfaaa6f35c8f996d68025deefff2b4dda79318"}, + {file = "boto3-1.43.72-py3-none-any.whl", hash = "sha256:f1bbbad5ed8d8a8c64edb0cd092dc443c95a85623b2ac88b6f6d633717605f00"}, + {file = "boto3-1.43.72.tar.gz", hash = "sha256:6280ce03cc85e9110fd9fb7e2fbf11eae0b1177cb041a0d69aa88edc9d178cf9"}, ] [package.dependencies] -botocore = ">=1.42.40,<1.43.0" +botocore = ">=1.43.72,<1.44.0" jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.16.0,<0.17.0" +s3transfer = ">=0.19.0,<0.20.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.42.40" +version = "1.43.72" description = "Low-level, data-driven core of boto 3." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "botocore-1.42.40-py3-none-any.whl", hash = "sha256:b115cdfece8162cb30f387fdff2ee4693713744c97ebb4b89742e53675dc521c"}, - {file = "botocore-1.42.40.tar.gz", hash = "sha256:6cfa07cf35ad477daef4920324f6d81b8d3a10a35baeafaa5fca22fb3ad225e2"}, + {file = "botocore-1.43.72-py3-none-any.whl", hash = "sha256:de5a1bcf8d7602c6cefc15016f15dad82981e339192531f31fa9483e11feea47"}, + {file = "botocore-1.43.72.tar.gz", hash = "sha256:1b878c69081e8e9d55aa4c0d85683e7b07f0e274a5554662f9507a46641be3d2"}, ] [package.dependencies] jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" -urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} +urllib3 = ">=1.25.4,<2.2.0 || >2.2.0,<3" [package.extras] -crt = ["awscrt (==0.29.2)"] +crt = ["awscrt (==0.36.0)"] [[package]] name = "brotli" @@ -411,21 +455,26 @@ files = [ [[package]] name = "brotlicffi" -version = "1.2.0.0" +version = "1.2.0.1" description = "Python CFFI bindings to the Brotli library" optional = false python-versions = ">=3.8" files = [ - {file = "brotlicffi-1.2.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:9458d08a7ccde8e3c0afedbf2c70a8263227a68dea5ab13590593f4c0a4fd5f4"}, - {file = "brotlicffi-1.2.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:84e3d0020cf1bd8b8131f4a07819edee9f283721566fe044a20ec792ca8fd8b7"}, - {file = "brotlicffi-1.2.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33cfb408d0cff64cd50bef268c0fed397c46fbb53944aa37264148614a62e990"}, - {file = "brotlicffi-1.2.0.0-cp38-abi3-win32.whl", hash = "sha256:23e5c912fdc6fd37143203820230374d24babd078fc054e18070a647118158f6"}, - {file = "brotlicffi-1.2.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:f139a7cdfe4ae7859513067b736eb44d19fae1186f9e99370092f6915216451b"}, - {file = "brotlicffi-1.2.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fa102a60e50ddbd08de86a63431a722ea216d9bc903b000bf544149cc9b823dc"}, - {file = "brotlicffi-1.2.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d3c4332fc808a94e8c1035950a10d04b681b03ab585ce897ae2a360d479037c"}, - {file = "brotlicffi-1.2.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb4eb5830026b79a93bf503ad32b2c5257315e9ffc49e76b2715cffd07c8e3db"}, - {file = "brotlicffi-1.2.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3832c66e00d6d82087f20a972b2fc03e21cd99ef22705225a6f8f418a9158ecc"}, - {file = "brotlicffi-1.2.0.0.tar.gz", hash = "sha256:34345d8d1f9d534fcac2249e57a4c3c8801a33c9942ff9f8574f67a175e17adb"}, + {file = "brotlicffi-1.2.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c85e65913cf2b79c57a3fdd05b98d9731d9255dc0cb696b09376cc091b9cddd"}, + {file = "brotlicffi-1.2.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:535f2d05d0273408abc13fc0eebb467afac17b0ad85090c8913690d40207dac5"}, + {file = "brotlicffi-1.2.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce17eb798ca59ecec67a9bb3fd7a4304e120d1cd02953ce522d959b9a84d58ac"}, + {file = "brotlicffi-1.2.0.1-cp314-cp314t-win32.whl", hash = "sha256:3c9544f83cb715d95d7eab3af4adbbef8b2093ad6382288a83b3a25feb1a57ec"}, + {file = "brotlicffi-1.2.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:625f8115d32ae9c0740d01ea51518437c3fbaa3e78d41cb18459f6f7ac326000"}, + {file = "brotlicffi-1.2.0.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:91ba5f0ccc040f6ff8f7efaf839f797723d03ed46acb8ae9408f99ffd2572cf4"}, + {file = "brotlicffi-1.2.0.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9a670c6811af30a4bd42d7116dc5895d3b41beaa8ed8a89050447a0181f5ce"}, + {file = "brotlicffi-1.2.0.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3314a3476f59e5443f9f72a6dff16edc0c3463c9b318feaef04ae3e4683f5a"}, + {file = "brotlicffi-1.2.0.1-cp38-abi3-win32.whl", hash = "sha256:82ea52e2b5d3145b6c406ebd3efb0d55db718b7ad996bd70c62cec0439de1187"}, + {file = "brotlicffi-1.2.0.1-cp38-abi3-win_amd64.whl", hash = "sha256:da2e82a08e7778b8bc539d27ca03cdd684113e81394bfaaad8d0dfc6a17ddede"}, + {file = "brotlicffi-1.2.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e015af99584c6db1490a69a210c765953e473e63adc2d891ac3062a737c9e851"}, + {file = "brotlicffi-1.2.0.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37cb587d32bf7168e2218c455e22e409ad1f3157c6c71945879a311f3e6b6abf"}, + {file = "brotlicffi-1.2.0.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d6ba65dd528892b4d9960beba2ae011a753620bcfc66cf6fa3cee18d7b0baa4"}, + {file = "brotlicffi-1.2.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2a5575653b0672638ba039b82fda56854934d7a6a24d4b8b5033f73ab43cbc1"}, + {file = "brotlicffi-1.2.0.1.tar.gz", hash = "sha256:c20d5c596278307ad06414a6d95a892377ea274a5c6b790c2548c009385d621c"}, ] [package.dependencies] @@ -436,117 +485,133 @@ cffi = [ [[package]] name = "cachetools" -version = "7.0.0" +version = "7.1.7" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.10" files = [ - {file = "cachetools-7.0.0-py3-none-any.whl", hash = "sha256:d52fef60e6e964a1969cfb61ccf6242a801b432790fe520d78720d757c81cbd2"}, - {file = "cachetools-7.0.0.tar.gz", hash = "sha256:a9abf18ff3b86c7d05b27ead412e235e16ae045925e531fae38d5fada5ed5b08"}, + {file = "cachetools-7.1.7-py3-none-any.whl", hash = "sha256:ef98ef375ad188819ef2f9b3645e3987f4b8c5b7550e436ad998c2de78296df0"}, + {file = "cachetools-7.1.7.tar.gz", hash = "sha256:a3e2a00b14d8f8a6b70c1dae7b4685e7ad3bc965c5b42124a2d6ce895da6cf50"}, ] [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" files = [ - {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, - {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, + {file = "certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775"}, + {file = "certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55"}, ] [[package]] name = "cffi" -version = "2.0.0" +version = "2.1.1" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, - {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, - {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, - {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, - {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, - {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, - {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, - {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, - {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, - {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, - {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, - {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, - {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, - {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, - {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, - {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, - {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, - {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, - {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, - {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, - {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, - {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, - {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, - {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, - {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, - {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, - {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, - {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, - {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, - {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, - {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, - {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, - {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, - {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, - {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, - {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, - {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, - {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, - {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, - {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, - {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, - {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, + {file = "cffi-2.1.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be"}, + {file = "cffi-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b"}, + {file = "cffi-2.1.1-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004"}, + {file = "cffi-2.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9"}, + {file = "cffi-2.1.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98"}, + {file = "cffi-2.1.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9"}, + {file = "cffi-2.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6"}, + {file = "cffi-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf"}, + {file = "cffi-2.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659"}, + {file = "cffi-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9"}, + {file = "cffi-2.1.1-cp310-cp310-win32.whl", hash = "sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41"}, + {file = "cffi-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1"}, + {file = "cffi-2.1.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12"}, + {file = "cffi-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1"}, + {file = "cffi-2.1.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0"}, + {file = "cffi-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813"}, + {file = "cffi-2.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990"}, + {file = "cffi-2.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af"}, + {file = "cffi-2.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632"}, + {file = "cffi-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd"}, + {file = "cffi-2.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a"}, + {file = "cffi-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa"}, + {file = "cffi-2.1.1-cp311-cp311-win32.whl", hash = "sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3"}, + {file = "cffi-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0"}, + {file = "cffi-2.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455"}, + {file = "cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0"}, + {file = "cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf"}, + {file = "cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a"}, + {file = "cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890"}, + {file = "cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50"}, + {file = "cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e"}, + {file = "cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf"}, + {file = "cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517"}, + {file = "cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735"}, + {file = "cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e"}, + {file = "cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a"}, + {file = "cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80"}, + {file = "cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e"}, + {file = "cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c"}, + {file = "cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6"}, + {file = "cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971"}, + {file = "cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c"}, + {file = "cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125"}, + {file = "cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264"}, + {file = "cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3"}, + {file = "cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2"}, + {file = "cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b"}, + {file = "cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7"}, + {file = "cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac"}, + {file = "cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d"}, + {file = "cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973"}, + {file = "cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c"}, + {file = "cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb"}, + {file = "cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54"}, + {file = "cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72"}, + {file = "cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1"}, + {file = "cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062"}, + {file = "cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03"}, + {file = "cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96"}, + {file = "cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527"}, + {file = "cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13"}, + {file = "cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c"}, + {file = "cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48"}, + {file = "cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836"}, + {file = "cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3"}, + {file = "cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2"}, + {file = "cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94"}, + {file = "cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc"}, + {file = "cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29"}, + {file = "cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676"}, + {file = "cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e"}, + {file = "cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f"}, + {file = "cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4"}, + {file = "cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e"}, + {file = "cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5"}, + {file = "cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d"}, + {file = "cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b"}, + {file = "cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4"}, + {file = "cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8"}, + {file = "cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6"}, + {file = "cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80"}, + {file = "cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779"}, + {file = "cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399"}, + {file = "cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688"}, + {file = "cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7"}, + {file = "cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac"}, + {file = "cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960"}, + {file = "cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1"}, + {file = "cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc"}, + {file = "cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab"}, + {file = "cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e"}, + {file = "cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358"}, + {file = "cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231"}, + {file = "cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6"}, + {file = "cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94"}, + {file = "cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5"}, + {file = "cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66"}, + {file = "cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3"}, + {file = "cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692"}, + {file = "cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be"}, ] [package.dependencies] @@ -554,135 +619,194 @@ pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} [[package]] name = "charset-normalizer" -version = "3.4.4" +version = "3.5.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" files = [ - {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, - {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, - {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1ee1e296209fdce05b81b663250eefa02213a2da7b41bf26f7829b8ba3545aa"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9fbdce1e47394b09bc9f26ab117dfc8d6491977a11d86f592bb42c779db2fda"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:00668ebb0609751758682eb0b5857e7c35b9f00e84dfdef062e103244ec94d45"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba2f37ee79e6338845261a3c5b1784e5d1acdff2c0785b284f1b633033d136ab"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ce854f5f478050ade5a238731c4ca985a7d3b3cb53ff600a9b5c3b689b5f0a7a"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96eefc178f8636b9c760c5829345307fd81cfae9ab1e80997dbddeb0f54ee9a3"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:366ec70f5547c640d3ce1985722490f23faf4eb5216a7eeba78277490e78dacb"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:950f23cb393f85543777b0433f082cddd25b51ab398eac7971146495679efe5f"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1dcc36dcb96abc02236e182d17e0f71430152a6c2c7447421da2d2dc144edea"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:07ffd07412fc5d5e84cd8952acf9ff7e4ed7a708e69d1bada19d8ba91711353f"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f5542f9b941279d82d41eb0aa9f98eba36fe4df5c7086c651df7944935b37182"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a545775cfe815855ea32d7c27731d79da358ef2055b4a25830231b1622dd18aa"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:494b70049a4d69aec6e8137c13af4cf8db8c9f9820a1392ac293b0dd2987a818"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-win32.whl", hash = "sha256:94fbf1c0c6cc0d3d5e50f9a9313a8cdca90dd696d34b381cd1704f8c9e939f20"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:be47f99644b208bff7766314013f9acf57b056b04191d570d68ad14022cf5b1d"}, + {file = "charset_normalizer-3.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:a6d095662e73e74f0a49988e0593373e243e3a52e27bfeea0a859e88acf4a0f5"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-win32.whl", hash = "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9"}, + {file = "charset_normalizer-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2"}, + {file = "charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6"}, + {file = "charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b"}, + {file = "charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00"}, + {file = "charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-win32.whl", hash = "sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712"}, + {file = "charset_normalizer-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-win32.whl", hash = "sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb"}, + {file = "charset_normalizer-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959"}, + {file = "charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85de3134b5379856e323ba37c19c9256d39425f7b76a63af52b09fb4664c2e8f"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e5e1224c0a6a90e05843e07adfec669edebec17801c67072f51e59561d63c0b"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5e2d0e146dcb57034f8b97dc58d2d512cb90aba253960ce449f695fec6a82c6f"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e06efa066f7dbadbc84ebc126a97c452a6451dfcf589d89d788484949e1cf795"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:994e883d17c559cdfd38c84003c8b27d25424a1077272a17e7cd27bfe0bf57b2"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:789b8982559ae28dad2356519f841655756cdcd96616410590ae0b17454ee64f"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a951ad59cad9145664a730d3036b40b844e74d2d3683da40111463cd3a83845d"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:55261ac0d2941c42f196dd576f543d87a8ee03cd6f5e30dfb4d807b2e3b9121a"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5ca0555312ae2fe82715cada7fac375530c2f3349e1eaa1bcb33d0283ac79a18"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2e9cf9253119d8e5d111f05d71626786fd3d6193817316eab1ca088cdb8593cf"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:433c5a81eade63b47e522303bad236f59dba55ea6951746f5558355eeed8c75d"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:13e3afe97712e8887cd516e960c63f0b93122971e5b5e4b2622fe7701771e838"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb12fb2ba69ffa05f8695f61c69e591dc4b4a12ac3757ac8af8adb259bf56d17"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-win32.whl", hash = "sha256:56490c595a28b1bb27dfc583e816152a9767721ef58b2c03b13f954d2f707420"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:012a22b88a77ca2e59b98ac5889b0deb604147666032f45e6d6e217634d2550d"}, + {file = "charset_normalizer-3.5.1-cp39-cp39-win_arm64.whl", hash = "sha256:29880d17a8eb0b5cfdfd8944b468322928059aa35f1f5fa8ff22b149ec0b42f8"}, + {file = "charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6"}, + {file = "charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3"}, ] [[package]] name = "click" -version = "8.3.1" +version = "8.4.2" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" files = [ - {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, - {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, + {file = "click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76"}, + {file = "click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6"}, ] [package.dependencies] @@ -710,103 +834,76 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "colorlog" -version = "6.10.1" -description = "Add colours to the output of Python's logging module." -optional = false -python-versions = ">=3.6" -files = [ - {file = "colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c"}, - {file = "colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} - -[package.extras] -development = ["black", "flake8", "mypy", "pytest", "types-colorama"] - [[package]] name = "cryptography" -version = "46.0.4" +version = "50.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = "!=3.9.0,!=3.9.1,>=3.8" -files = [ - {file = "cryptography-46.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:281526e865ed4166009e235afadf3a4c4cba6056f99336a99efba65336fd5485"}, - {file = "cryptography-46.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f14fba5bf6f4390d7ff8f086c566454bff0411f6d8aa7af79c88b6f9267aecc"}, - {file = "cryptography-46.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47bcd19517e6389132f76e2d5303ded6cf3f78903da2158a671be8de024f4cd0"}, - {file = "cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:01df4f50f314fbe7009f54046e908d1754f19d0c6d3070df1e6268c5a4af09fa"}, - {file = "cryptography-46.0.4-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5aa3e463596b0087b3da0dbe2b2487e9fc261d25da85754e30e3b40637d61f81"}, - {file = "cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0a9ad24359fee86f131836a9ac3bffc9329e956624a2d379b613f8f8abaf5255"}, - {file = "cryptography-46.0.4-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:dc1272e25ef673efe72f2096e92ae39dea1a1a450dd44918b15351f72c5a168e"}, - {file = "cryptography-46.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:de0f5f4ec8711ebc555f54735d4c673fc34b65c44283895f1a08c2b49d2fd99c"}, - {file = "cryptography-46.0.4-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:eeeb2e33d8dbcccc34d64651f00a98cb41b2dc69cef866771a5717e6734dfa32"}, - {file = "cryptography-46.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3d425eacbc9aceafd2cb429e42f4e5d5633c6f873f5e567077043ef1b9bbf616"}, - {file = "cryptography-46.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91627ebf691d1ea3976a031b61fb7bac1ccd745afa03602275dda443e11c8de0"}, - {file = "cryptography-46.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2d08bc22efd73e8854b0b7caff402d735b354862f1145d7be3b9c0f740fef6a0"}, - {file = "cryptography-46.0.4-cp311-abi3-win32.whl", hash = "sha256:82a62483daf20b8134f6e92898da70d04d0ef9a75829d732ea1018678185f4f5"}, - {file = "cryptography-46.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:6225d3ebe26a55dbc8ead5ad1265c0403552a63336499564675b29eb3184c09b"}, - {file = "cryptography-46.0.4-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:485e2b65d25ec0d901bca7bcae0f53b00133bf3173916d8e421f6fddde103908"}, - {file = "cryptography-46.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:078e5f06bd2fa5aea5a324f2a09f914b1484f1d0c2a4d6a8a28c74e72f65f2da"}, - {file = "cryptography-46.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dce1e4f068f03008da7fa51cc7abc6ddc5e5de3e3d1550334eaf8393982a5829"}, - {file = "cryptography-46.0.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2067461c80271f422ee7bdbe79b9b4be54a5162e90345f86a23445a0cf3fd8a2"}, - {file = "cryptography-46.0.4-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:c92010b58a51196a5f41c3795190203ac52edfd5dc3ff99149b4659eba9d2085"}, - {file = "cryptography-46.0.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:829c2b12bbc5428ab02d6b7f7e9bbfd53e33efd6672d21341f2177470171ad8b"}, - {file = "cryptography-46.0.4-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:62217ba44bf81b30abaeda1488686a04a702a261e26f87db51ff61d9d3510abd"}, - {file = "cryptography-46.0.4-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:9c2da296c8d3415b93e6053f5a728649a87a48ce084a9aaf51d6e46c87c7f2d2"}, - {file = "cryptography-46.0.4-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9b34d8ba84454641a6bf4d6762d15847ecbd85c1316c0a7984e6e4e9f748ec2e"}, - {file = "cryptography-46.0.4-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:df4a817fa7138dd0c96c8c8c20f04b8aaa1fac3bbf610913dcad8ea82e1bfd3f"}, - {file = "cryptography-46.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b1de0ebf7587f28f9190b9cb526e901bf448c9e6a99655d2b07fff60e8212a82"}, - {file = "cryptography-46.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9b4d17bc7bd7cdd98e3af40b441feaea4c68225e2eb2341026c84511ad246c0c"}, - {file = "cryptography-46.0.4-cp314-cp314t-win32.whl", hash = "sha256:c411f16275b0dea722d76544a61d6421e2cc829ad76eec79280dbdc9ddf50061"}, - {file = "cryptography-46.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:728fedc529efc1439eb6107b677f7f7558adab4553ef8669f0d02d42d7b959a7"}, - {file = "cryptography-46.0.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a9556ba711f7c23f77b151d5798f3ac44a13455cc68db7697a1096e6d0563cab"}, - {file = "cryptography-46.0.4-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8bf75b0259e87fa70bddc0b8b4078b76e7fd512fd9afae6c1193bcf440a4dbef"}, - {file = "cryptography-46.0.4-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c268a3490df22270955966ba236d6bc4a8f9b6e4ffddb78aac535f1a5ea471d"}, - {file = "cryptography-46.0.4-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:812815182f6a0c1d49a37893a303b44eaac827d7f0d582cecfc81b6427f22973"}, - {file = "cryptography-46.0.4-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a90e43e3ef65e6dcf969dfe3bb40cbf5aef0d523dff95bfa24256be172a845f4"}, - {file = "cryptography-46.0.4-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a05177ff6296644ef2876fce50518dffb5bcdf903c85250974fc8bc85d54c0af"}, - {file = "cryptography-46.0.4-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:daa392191f626d50f1b136c9b4cf08af69ca8279d110ea24f5c2700054d2e263"}, - {file = "cryptography-46.0.4-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e07ea39c5b048e085f15923511d8121e4a9dc45cee4e3b970ca4f0d338f23095"}, - {file = "cryptography-46.0.4-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d5a45ddc256f492ce42a4e35879c5e5528c09cd9ad12420828c972951d8e016b"}, - {file = "cryptography-46.0.4-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:6bb5157bf6a350e5b28aee23beb2d84ae6f5be390b2f8ee7ea179cda077e1019"}, - {file = "cryptography-46.0.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd5aba870a2c40f87a3af043e0dee7d9eb02d4aff88a797b48f2b43eff8c3ab4"}, - {file = "cryptography-46.0.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:93d8291da8d71024379ab2cb0b5c57915300155ad42e07f76bea6ad838d7e59b"}, - {file = "cryptography-46.0.4-cp38-abi3-win32.whl", hash = "sha256:0563655cb3c6d05fb2afe693340bc050c30f9f34e15763361cf08e94749401fc"}, - {file = "cryptography-46.0.4-cp38-abi3-win_amd64.whl", hash = "sha256:fa0900b9ef9c49728887d1576fd8d9e7e3ea872fa9b25ef9b64888adc434e976"}, - {file = "cryptography-46.0.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:766330cce7416c92b5e90c3bb71b1b79521760cdcfc3a6a1a182d4c9fab23d2b"}, - {file = "cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c236a44acfb610e70f6b3e1c3ca20ff24459659231ef2f8c48e879e2d32b73da"}, - {file = "cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8a15fb869670efa8f83cbffbc8753c1abf236883225aed74cd179b720ac9ec80"}, - {file = "cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:fdc3daab53b212472f1524d070735b2f0c214239df131903bae1d598016fa822"}, - {file = "cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:44cc0675b27cadb71bdbb96099cca1fa051cd11d2ade09e5cd3a2edb929ed947"}, - {file = "cryptography-46.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be8c01a7d5a55f9a47d1888162b76c8f49d62b234d88f0ff91a9fbebe32ffbc3"}, - {file = "cryptography-46.0.4.tar.gz", hash = "sha256:bfd019f60f8abc2ed1b9be4ddc21cfef059c841d86d710bb69909a688cbb8f59"}, +python-versions = "!=3.9.0,!=3.9.1,>=3.9" +files = [ + {file = "cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03"}, + {file = "cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645"}, + {file = "cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7"}, + {file = "cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3"}, + {file = "cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f"}, + {file = "cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae"}, + {file = "cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a"}, + {file = "cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987"}, + {file = "cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169"}, + {file = "cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f"}, + {file = "cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105"}, + {file = "cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef"}, + {file = "cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30"}, + {file = "cryptography-50.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41"}, + {file = "cryptography-50.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc"}, + {file = "cryptography-50.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f"}, + {file = "cryptography-50.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3"}, + {file = "cryptography-50.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533"}, + {file = "cryptography-50.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037"}, + {file = "cryptography-50.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f"}, + {file = "cryptography-50.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11"}, + {file = "cryptography-50.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d"}, + {file = "cryptography-50.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c"}, + {file = "cryptography-50.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c"}, + {file = "cryptography-50.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95"}, + {file = "cryptography-50.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269"}, + {file = "cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07"}, + {file = "cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3"}, + {file = "cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f"}, + {file = "cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5"}, + {file = "cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f"}, + {file = "cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025"}, + {file = "cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a"}, + {file = "cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b"}, + {file = "cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708"}, + {file = "cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47"}, + {file = "cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9"}, + {file = "cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7"}, + {file = "cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba"}, + {file = "cryptography-50.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5e34edd123674534acd70147f0ca331eaa2c74e6325fb2028c886aa26ba0b68c"}, + {file = "cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8eb5e1172eb569ea8a872796576e6a67c276351728b6455d5beb01242b027c6a"}, + {file = "cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:910d11e1a385c654bf738bf3e6b8e6ed5de0f5610fcae2be9e5b398d8081d20e"}, + {file = "cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:62598a8a57f815db4c6259a4e97d857dab56697e7de8e8ab02352ab74da1995d"}, + {file = "cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:07479a1cb08219ab719147e742e76090c9c773321959bb94946fffdd397a6437"}, + {file = "cryptography-50.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c99c003e088647b8a5b7c145d6f78c335f6348332b62e142d411c4b63d1460b9"}, + {file = "cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9"}, ] [package.dependencies] -cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9\" and platform_python_implementation != \"PyPy\""} +cffi = {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] -docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox[uv] (>=2024.4.15)"] -pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] -sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==46.0.4)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] -test-randomorder = ["pytest-randomly"] [[package]] name = "ddgs" -version = "9.10.0" +version = "9.15.0" description = "Dux Distributed Global Search. A metasearch library that aggregates results from diverse web search services." optional = false python-versions = ">=3.10" files = [ - {file = "ddgs-9.10.0-py3-none-any.whl", hash = "sha256:81233d79309836eb03e7df2a0d2697adc83c47c342713132c0ba618f1f2c6eee"}, - {file = "ddgs-9.10.0.tar.gz", hash = "sha256:d9381ff75bdf1ad6691d3d1dc2be12be190d1d32ecd24f1002c492143c52c34f"}, + {file = "ddgs-9.15.0-py3-none-any.whl", hash = "sha256:2c6cce11d8625a030ed471265230dd6d1f5e12a6efecde5dfa3a1b33882888e0"}, + {file = "ddgs-9.15.0.tar.gz", hash = "sha256:12c4148da66525031214279d3ecb5170778a484f2616cac12667d0824818d07d"}, ] [package.dependencies] @@ -814,10 +911,12 @@ click = ">=8.1.8" fake-useragent = ">=2.2.0" httpx = {version = ">=0.28.1", extras = ["brotli", "http2", "socks"]} lxml = ">=4.9.4" -primp = ">=0.15.0" +primp = ">=1.2.3" [package.extras] -dev = ["lxml-stubs", "mypy (>=1.17.1)", "pre-commit", "pytest (>=8.4.1)", "pytest-dependency (>=0.6.0)", "ruff (>=0.13.0)", "types-Pygments", "types-pexpect"] +api = ["fastapi (>=0.135.1)", "uvicorn[standard] (>=0.41.0)"] +dev = ["lxml-stubs", "mypy (>=1.17.1)", "prek", "pytest (>=8.4.1)", "pytest-trio", "ruff (>=0.13.0)", "types-PySocks", "types-PyYAML", "types-Pygments", "types-colorama", "types-decorator", "types-jsonschema", "types-pexpect", "types-psutil", "types-pyasn1", "types-ujson"] +mcp = ["mcp (>=2.0)"] [[package]] name = "diskcache" @@ -843,43 +942,42 @@ files = [ [[package]] name = "dspy" -version = "3.1.3" +version = "3.3.0" description = "DSPy" optional = false python-versions = "<3.15,>=3.10" files = [ - {file = "dspy-3.1.3-py3-none-any.whl", hash = "sha256:26f983372ebb284324cc2162458f7bce509ef5ef7b48be4c9f490fa06ea73e37"}, - {file = "dspy-3.1.3.tar.gz", hash = "sha256:e2fd9edc8678e0abcacd5d7b901f37b84a9f48a3c50718fc7fee95a492796019"}, + {file = "dspy-3.3.0-py3-none-any.whl", hash = "sha256:358cbfb15d13246dc4a289bb2350c0ee602260c8a3869f7f63a48a9d2233e48c"}, + {file = "dspy-3.3.0.tar.gz", hash = "sha256:39aa9531391accda8acd7903b52f3c9d2efe462d4bab0c2256db5352e7392754"}, ] [package.dependencies] anyio = "*" -asyncer = "0.0.8" cachetools = ">=5.5.0" -cloudpickle = ">=3.0.0" +cloudpickle = ">=3.1.2" diskcache = ">=5.6.0" -gepa = {version = "0.0.26", extras = ["dspy"]} +gepa = {version = "0.1.1", extras = ["dspy"]} json-repair = ">=0.54.2" -litellm = ">=1.64.0" +litellm = ">=1.65.8" mcp = {version = "*", optional = true, markers = "python_version >= \"3.10\" and extra == \"mcp\""} -numpy = ">=1.26.0" -openai = ">=0.28.1" -optuna = ">=3.4.0" +openai = ">=1.66.2" orjson = ">=3.9.0" pydantic = ">=2.0" regex = ">=2023.10.3" requests = ">=2.31.0" tenacity = ">=8.2.3" tqdm = ">=4.66.1" -xxhash = ">=3.5.0" [package.extras] anthropic = ["anthropic (>=0.18.0,<1.0.0)"] -dev = ["build (>=1.0.3)", "datamodel_code_generator (>=0.26.3)", "litellm (>=1.64.0)", "litellm[proxy] (>=1.64.0)", "pillow (>=10.1.0)", "pre-commit (>=3.7.0)", "pytest (>=6.2.5)", "pytest-asyncio (>=0.26.0)", "pytest-mock (>=3.12.0)", "ruff (>=0.3.0)"] -langchain = ["langchain_core"] +dev = ["build (>=1.0.3)", "datamodel_code_generator (>=0.26.3)", "litellm (>=1.65.8)", "litellm[proxy] (>=1.65.8)", "numpy (>=1.26.0)", "pillow (>=10.1.0)", "pre-commit (>=3.7.0)", "pytest (>=6.2.5)", "pytest-asyncio (>=0.26.0)", "pytest-mock (>=3.12.0)", "pytest-xdist (>=3.5.0)", "ruff (>=0.3.0)"] +langchain = ["langchain_core (>=0.3.0)"] +litellm = ["litellm (>=1.65.8)"] mcp = ["mcp"] -test-extras = ["datasets (>=2.14.6)", "langchain_core", "mcp", "optuna (>=3.4.0)", "pandas (>=2.1.1)"] -weaviate = ["weaviate-client (>=4.5.4,<4.6.0)"] +numpy = ["numpy (>=1.26.0)"] +optuna = ["optuna (>=3.4.0)"] +test-extras = ["datasets (>=2.14.6)", "langchain_core (>=0.3.0)", "mcp", "numpy (>=1.26.0)", "optuna (>=3.4.0)", "pandas (>=2.1.1)"] +weaviate = ["weaviate-client (>=4.5.4,<4.22.0)"] [[package]] name = "fake-useragent" @@ -981,13 +1079,13 @@ files = [ [[package]] name = "filelock" -version = "3.20.3" +version = "3.32.3" description = "A platform independent file lock." optional = false python-versions = ">=3.10" files = [ - {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, - {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, + {file = "filelock-3.32.3-py3-none-any.whl", hash = "sha256:7f0ca4bcc0e181c60dbbd8aa9ab5b120ebb99e4e064e83636340056f833a1f09"}, + {file = "filelock-3.32.3.tar.gz", hash = "sha256:0ffa185a3540854c95caa7fa76b76cb219d907415e2c5dc9af25fd970563487f"}, ] [[package]] @@ -1131,13 +1229,13 @@ files = [ [[package]] name = "fsspec" -version = "2026.1.0" +version = "2026.7.0" description = "File-system specification" optional = false python-versions = ">=3.10" files = [ - {file = "fsspec-2026.1.0-py3-none-any.whl", hash = "sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc"}, - {file = "fsspec-2026.1.0.tar.gz", hash = "sha256:e987cb0496a0d81bba3a9d1cee62922fb395e7d4c3b575e57f547953334fe07b"}, + {file = "fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279"}, + {file = "fsspec-2026.7.0.tar.gz", hash = "sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88"}, ] [package.extras] @@ -1148,41 +1246,42 @@ dask = ["dask", "distributed"] dev = ["pre-commit", "ruff (>=0.5)"] doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] dropbox = ["dropbox", "dropboxdrivefs", "requests"] -full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs (>2024.2.0)", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs (>2024.2.0)", "smbprotocol", "tqdm"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs (>=2026.4.0)", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs (>=2026.6.0)", "smbprotocol", "tqdm"] fuse = ["fusepy"] -gcs = ["gcsfs"] +gcs = ["gcsfs (>=2026.4.0)"] git = ["pygit2"] github = ["requests"] -gs = ["gcsfs"] +gs = ["gcsfs (>=2026.4.0)"] gui = ["panel"] hdfs = ["pyarrow (>=1)"] http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] libarchive = ["libarchive-c"] oci = ["ocifs"] -s3 = ["s3fs"] +s3 = ["s3fs (>=2026.6.0)"] sftp = ["paramiko"] smb = ["smbprotocol"] ssh = ["paramiko"] test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] -test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "backports-zstd", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "backports-zstd", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs (>=2026.4.0)", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas (<3.0.0)", "panel", "paramiko", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "s3fs (>=2026.6.0)", "smbprotocol", "tqdm", "urllib3", "zarr (<3.2.0)", "zstandard"] tqdm = ["tqdm"] [[package]] name = "gepa" -version = "0.0.26" +version = "0.1.1" description = "A framework for optimizing textual system components (AI prompts, code snippets, etc.) using LLM-based reflection and Pareto-efficient evolutionary search." optional = false python-versions = "<3.15,>=3.10" files = [ - {file = "gepa-0.0.26-py3-none-any.whl", hash = "sha256:331e40d8693a4192de2eb3b2b4df10d410ead49173f748d50c32a035cf746e63"}, - {file = "gepa-0.0.26.tar.gz", hash = "sha256:0119ca8022e93b6236bc154a57bb910bdb117485dc067d77777933dd3e9e9ad8"}, + {file = "gepa-0.1.1-py3-none-any.whl", hash = "sha256:71ead7c591eafcc727b83509cdc4182f20264800a6ddf8520d61419daeb47466"}, + {file = "gepa-0.1.1.tar.gz", hash = "sha256:643fda01c23de4c9f01306e01305dd69facc29bcb34ad59e4cd07e6621d34aa1"}, ] [package.extras] build = ["build", "packaging", "requests", "semver", "setuptools (>=77.0.1)", "twine", "wheel"] dev = ["build (>=1.0.3)", "gepa[build]", "gepa[test]", "pre-commit", "ruff (>=0.3.0)"] -full = ["datasets (>=2.14.6)", "datasets (>=4.5.0)", "litellm (>=1.64.0)", "litellm (>=1.81.0)", "mlflow (>=3.0.0)", "mlflow-skinny (>=3.8.1)", "pyarrow (>=22.0.0)", "pydantic (>=2.12.0)", "tiktoken (>=0.12.0)", "tqdm (>=4.66.1)", "wandb", "wandb (>=0.23.0)"] +full = ["cloudpickle (>=3.0.0)", "datasets (>=2.14.6)", "datasets (>=4.5.0)", "litellm (>=1.64.0)", "litellm (>=1.81.0)", "mlflow (>=3.0.0)", "mlflow-skinny (>=3.8.1)", "pyarrow (>=22.0.0)", "pydantic (>=2.12.0)", "tiktoken (>=0.12.0)", "tqdm (>=4.66.1)", "wandb", "wandb (>=0.23.0)"] +gskill = ["docker", "gepa[full]", "python-dotenv", "pyyaml", "swesmith"] test = ["gepa[full]", "pyright", "pytest"] [[package]] @@ -1201,87 +1300,21 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.46" +version = "3.1.59" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ - {file = "gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058"}, - {file = "gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f"}, + {file = "gitpython-3.1.59-py3-none-any.whl", hash = "sha256:67a82f537384578643624c8b2c531938a9b82be431663e575dcf638526631d4c"}, + {file = "gitpython-3.1.59.tar.gz", hash = "sha256:0a1475cfdc38a5bfba1a3e9a4a9da52a39749ecec322b772915c019f94e5b7e4"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy (==1.18.2)", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] - -[[package]] -name = "greenlet" -version = "3.3.1" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.10" -files = [ - {file = "greenlet-3.3.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:04bee4775f40ecefcdaa9d115ab44736cd4b9c5fba733575bfe9379419582e13"}, - {file = "greenlet-3.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e1457f4fed12a50e427988a07f0f9df53cf0ee8da23fab16e6732c2ec909d4"}, - {file = "greenlet-3.3.1-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:070472cd156f0656f86f92e954591644e158fd65aa415ffbe2d44ca77656a8f5"}, - {file = "greenlet-3.3.1-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1108b61b06b5224656121c3c8ee8876161c491cbe74e5c519e0634c837cf93d5"}, - {file = "greenlet-3.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a300354f27dd86bae5fbf7002e6dd2b3255cd372e9242c933faf5e859b703fe"}, - {file = "greenlet-3.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e84b51cbebf9ae573b5fbd15df88887815e3253fc000a7d0ff95170e8f7e9729"}, - {file = "greenlet-3.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0093bd1a06d899892427217f0ff2a3c8f306182b8c754336d32e2d587c131b4"}, - {file = "greenlet-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:7932f5f57609b6a3b82cc11877709aa7a98e3308983ed93552a1c377069b20c8"}, - {file = "greenlet-3.3.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5fd23b9bc6d37b563211c6abbb1b3cab27db385a4449af5c32e932f93017080c"}, - {file = "greenlet-3.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f51496a0bfbaa9d74d36a52d2580d1ef5ed4fdfcff0a73730abfbbbe1403dd"}, - {file = "greenlet-3.3.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb0feb07fe6e6a74615ee62a880007d976cf739b6669cce95daa7373d4fc69c5"}, - {file = "greenlet-3.3.1-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67ea3fc73c8cd92f42467a72b75e8f05ed51a0e9b1d15398c913416f2dafd49f"}, - {file = "greenlet-3.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39eda9ba259cc9801da05351eaa8576e9aa83eb9411e8f0c299e05d712a210f2"}, - {file = "greenlet-3.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e2e7e882f83149f0a71ac822ebf156d902e7a5d22c9045e3e0d1daf59cee2cc9"}, - {file = "greenlet-3.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80aa4d79eb5564f2e0a6144fcc744b5a37c56c4a92d60920720e99210d88db0f"}, - {file = "greenlet-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:32e4ca9777c5addcbf42ff3915d99030d8e00173a56f80001fb3875998fe410b"}, - {file = "greenlet-3.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:da19609432f353fed186cc1b85e9440db93d489f198b4bdf42ae19cc9d9ac9b4"}, - {file = "greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975"}, - {file = "greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36"}, - {file = "greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba"}, - {file = "greenlet-3.3.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c620051669fd04ac6b60ebc70478210119c56e2d5d5df848baec4312e260e4ca"}, - {file = "greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336"}, - {file = "greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1"}, - {file = "greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149"}, - {file = "greenlet-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cc98b9c4e4870fa983436afa999d4eb16b12872fab7071423d5262fa7120d57a"}, - {file = "greenlet-3.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bfb2d1763d777de5ee495c85309460f6fd8146e50ec9d0ae0183dbf6f0a829d1"}, - {file = "greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3"}, - {file = "greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac"}, - {file = "greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd"}, - {file = "greenlet-3.3.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:12184c61e5d64268a160226fb4818af4df02cfead8379d7f8b99a56c3a54ff3e"}, - {file = "greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3"}, - {file = "greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951"}, - {file = "greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2"}, - {file = "greenlet-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:27289986f4e5b0edec7b5a91063c109f0276abb09a7e9bdab08437525977c946"}, - {file = "greenlet-3.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:2f080e028001c5273e0b42690eaf359aeef9cb1389da0f171ea51a5dc3c7608d"}, - {file = "greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5"}, - {file = "greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b"}, - {file = "greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e"}, - {file = "greenlet-3.3.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d"}, - {file = "greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f"}, - {file = "greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683"}, - {file = "greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1"}, - {file = "greenlet-3.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:96aff77af063b607f2489473484e39a0bbae730f2ea90c9e5606c9b73c44174a"}, - {file = "greenlet-3.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:b066e8b50e28b503f604fa538adc764a638b38cf8e81e025011d26e8a627fa79"}, - {file = "greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242"}, - {file = "greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774"}, - {file = "greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97"}, - {file = "greenlet-3.3.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab"}, - {file = "greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2"}, - {file = "greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53"}, - {file = "greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249"}, - {file = "greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451"}, - {file = "greenlet-3.3.1.tar.gz", hash = "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil", "setuptools"] +doc = ["sphinx (>=7.4.7,<8)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] +test = ["basedpyright (==1.39.9)", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy (==1.18.2)", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] [[package]] name = "h11" @@ -1296,48 +1329,43 @@ files = [ [[package]] name = "h2" -version = "4.3.0" +version = "4.4.1" description = "Pure-Python HTTP/2 protocol implementation" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd"}, - {file = "h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1"}, + {file = "h2-4.4.1-py3-none-any.whl", hash = "sha256:0e25f1462b23c9cb82d9eb02e28bc706dac2a68cb457c6a0d74d63c8a2a5d0e6"}, + {file = "h2-4.4.1.tar.gz", hash = "sha256:4e866ffb1a869ae14dd9b5e6beb5c24a13da0495ad72b65925ded182521c1516"}, ] [package.dependencies] -hpack = ">=4.1,<5" +hpack = ">=4.2,<5" hyperframe = ">=6.1,<7" [[package]] name = "hf-xet" -version = "1.2.0" +version = "1.6.0" description = "Fast transfer of large files with the Hugging Face Hub." optional = false python-versions = ">=3.8" files = [ - {file = "hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ceeefcd1b7aed4956ae8499e2199607765fbd1c60510752003b6cc0b8413b649"}, - {file = "hf_xet-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b70218dd548e9840224df5638fdc94bd033552963cfa97f9170829381179c813"}, - {file = "hf_xet-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d40b18769bb9a8bc82a9ede575ce1a44c75eb80e7375a01d76259089529b5dc"}, - {file = "hf_xet-1.2.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd3a6027d59cfb60177c12d6424e31f4b5ff13d8e3a1247b3a584bf8977e6df5"}, - {file = "hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6de1fc44f58f6dd937956c8d304d8c2dea264c80680bcfa61ca4a15e7b76780f"}, - {file = "hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f182f264ed2acd566c514e45da9f2119110e48a87a327ca271027904c70c5832"}, - {file = "hf_xet-1.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:293a7a3787e5c95d7be1857358a9130694a9c6021de3f27fa233f37267174382"}, - {file = "hf_xet-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:10bfab528b968c70e062607f663e21e34e2bba349e8038db546646875495179e"}, - {file = "hf_xet-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a212e842647b02eb6a911187dc878e79c4aa0aa397e88dd3b26761676e8c1f8"}, - {file = "hf_xet-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30e06daccb3a7d4c065f34fc26c14c74f4653069bb2b194e7f18f17cbe9939c0"}, - {file = "hf_xet-1.2.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:29c8fc913a529ec0a91867ce3d119ac1aac966e098cf49501800c870328cc090"}, - {file = "hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e159cbfcfbb29f920db2c09ed8b660eb894640d284f102ada929b6e3dc410a"}, - {file = "hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c91d5ae931510107f148874e9e2de8a16052b6f1b3ca3c1b12f15ccb491390f"}, - {file = "hf_xet-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:210d577732b519ac6ede149d2f2f34049d44e8622bf14eb3d63bbcd2d4b332dc"}, - {file = "hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848"}, - {file = "hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4"}, - {file = "hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd"}, - {file = "hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c"}, - {file = "hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737"}, - {file = "hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865"}, - {file = "hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69"}, - {file = "hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f"}, + {file = "hf_xet-1.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:70cbb9c896901600128cb9b6f06e132954fbede1db30f31f7c6c63f84cb7c31d"}, + {file = "hf_xet-1.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:23379c2f9ec8696d952b16414a2bae72cad86a52df869b050698ba60f538c675"}, + {file = "hf_xet-1.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f7278c05c22fd60cb436cda1269649b3e81db65ecdc8496e5e164aa4143e7b"}, + {file = "hf_xet-1.6.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:948f15d3a9545cfe5932f6bd8b440f6ae630aee108f14b7bd6c561f7c2dcc522"}, + {file = "hf_xet-1.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5153e6bb103ad49d6ea9f1b2e230db5a2ea32551ad09a706d2f61d7c7c80d80e"}, + {file = "hf_xet-1.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:35cec30d75c6f9eb9c16a77cef68e85a103b72e24d4b473714ec9ff06428bab9"}, + {file = "hf_xet-1.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5789835d7c6bc9436962853192082374297fb72d7eff7e7762ec25ceb7e25338"}, + {file = "hf_xet-1.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:75765820ce4700db3750c94acc8fe27c5fae4c9ec000a0dbac3ca082acf97765"}, + {file = "hf_xet-1.6.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d"}, + {file = "hf_xet-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a"}, + {file = "hf_xet-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f"}, + {file = "hf_xet-1.6.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7"}, + {file = "hf_xet-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb"}, + {file = "hf_xet-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c"}, + {file = "hf_xet-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:fb4fadde1b2b70bf4c0c14a6dccbe7194b1c28947fefd5bbe3fed9d940676c3b"}, + {file = "hf_xet-1.6.0-cp38-abi3-win_arm64.whl", hash = "sha256:3dc3e35441ba395006af5aaacc40ef2e603c51ef46c3530b9156185f00935ea3"}, + {file = "hf_xet-1.6.0.tar.gz", hash = "sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef"}, ] [package.extras] @@ -1345,13 +1373,13 @@ tests = ["pytest"] [[package]] name = "hpack" -version = "4.1.0" +version = "4.2.0" description = "Pure-Python HPACK header encoding" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"}, - {file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"}, + {file = "hpack-4.2.0-py3-none-any.whl", hash = "sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986"}, + {file = "hpack-4.2.0.tar.gz", hash = "sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0"}, ] [[package]] @@ -1416,36 +1444,36 @@ files = [ [[package]] name = "huggingface-hub" -version = "1.3.7" +version = "1.27.0" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false -python-versions = ">=3.9.0" +python-versions = ">=3.10.0" files = [ - {file = "huggingface_hub-1.3.7-py3-none-any.whl", hash = "sha256:8155ce937038fa3d0cb4347d752708079bc85e6d9eb441afb44c84bcf48620d2"}, - {file = "huggingface_hub-1.3.7.tar.gz", hash = "sha256:5f86cd48f27131cdbf2882699cbdf7a67dd4cbe89a81edfdc31211f42e4a5fd1"}, + {file = "huggingface_hub-1.27.0-py3-none-any.whl", hash = "sha256:7df6827c2f956c60fbaa64646e979e566db76f619dd0a9729dfb8c5a3eb4f68d"}, + {file = "huggingface_hub-1.27.0.tar.gz", hash = "sha256:c1fed40ea82a6b41b477f5243546549b792ae0a93abcea608cff66089bf8f8df"}, ] [package.dependencies] -filelock = "*" +click = ">=8.4.2,<9.0.0" +filelock = ">=3.10.0" fsspec = ">=2023.5.0" -hf-xet = {version = ">=1.2.0,<2.0.0", markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""} +hf-xet = {version = ">=1.5.2,<2.0.0", markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""} httpx = ">=0.23.0,<1" packaging = ">=20.9" pyyaml = ">=5.1" -shellingham = "*" tqdm = ">=4.42.1" -typer-slim = "*" typing-extensions = ">=4.1.0" [package.extras] -all = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] -dev = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +all = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "duckdb", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (>=16.2)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +dev = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "duckdb", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (>=16.2)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] -hf-xet = ["hf-xet (>=1.2.0,<2.0.0)"] +gradio = ["gradio (>=5.0.0)", "requests"] +hf-xet = ["hf-xet (>=1.5.2,<2.0.0)"] mcp = ["mcp (>=1.8.0)"] oauth = ["authlib (>=1.3.2)", "fastapi", "httpx", "itsdangerous"] quality = ["libcst (>=1.4.0)", "mypy (==1.15.0)", "ruff (>=0.9.0)", "ty"] -testing = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +testing = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "duckdb", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (>=16.2)", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] torch = ["safetensors[torch]", "torch"] typing = ["types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] @@ -1462,40 +1490,40 @@ files = [ [[package]] name = "idna" -version = "3.11" +version = "3.18" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, - {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, + {file = "idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2"}, + {file = "idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848"}, ] [package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "importlib-metadata" -version = "8.7.1" +version = "8.9.0" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, - {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, + {file = "importlib_metadata-8.9.0-py3-none-any.whl", hash = "sha256:e0f761b6ea91ced3b0844c14c9d955224d538105921f8e6754c00f6ca79fba7f"}, + {file = "importlib_metadata-8.9.0.tar.gz", hash = "sha256:58850626cef4bd2df100378b0f2aea9724a7b92f10770d547725b047078f99ee"}, ] [package.dependencies] zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.14)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=3.4)"] perf = ["ipython"] -test = ["flufl.flake8", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["mypy (<1.19)", "pytest-mypy (>=1.0.1)"] +test = ["packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy (>=1.0.1)"] [[package]] name = "iniconfig" @@ -1527,113 +1555,116 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jiter" -version = "0.13.0" +version = "0.16.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" files = [ - {file = "jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e"}, - {file = "jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a"}, - {file = "jiter-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19928b5d1ce0ff8c1ee1b9bdef3b5bfc19e8304f1b904e436caf30bc15dc6cf5"}, - {file = "jiter-0.13.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:309549b778b949d731a2f0e1594a3f805716be704a73bf3ad9a807eed5eb5721"}, - {file = "jiter-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcdabaea26cb04e25df3103ce47f97466627999260290349a88c8136ecae0060"}, - {file = "jiter-0.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a377af27b236abbf665a69b2bdd680e3b5a0bd2af825cd3b81245279a7606c"}, - {file = "jiter-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe49d3ff6db74321f144dff9addd4a5874d3105ac5ba7c5b77fac099cfae31ae"}, - {file = "jiter-0.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2113c17c9a67071b0f820733c0893ed1d467b5fcf4414068169e5c2cabddb1e2"}, - {file = "jiter-0.13.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab1185ca5c8b9491b55ebf6c1e8866b8f68258612899693e24a92c5fdb9455d5"}, - {file = "jiter-0.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9621ca242547edc16400981ca3231e0c91c0c4c1ab8573a596cd9bb3575d5c2b"}, - {file = "jiter-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a7637d92b1c9d7a771e8c56f445c7f84396d48f2e756e5978840ecba2fac0894"}, - {file = "jiter-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c1b609e5cbd2f52bb74fb721515745b407df26d7b800458bd97cb3b972c29e7d"}, - {file = "jiter-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ea026e70a9a28ebbdddcbcf0f1323128a8db66898a06eaad3a4e62d2f554d096"}, - {file = "jiter-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66aa3e663840152d18cc8ff1e4faad3dd181373491b9cfdc6004b92198d67911"}, - {file = "jiter-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3524798e70655ff19aec58c7d05adb1f074fecff62da857ea9be2b908b6d701"}, - {file = "jiter-0.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec7e287d7fbd02cb6e22f9a00dd9c9cd504c40a61f2c61e7e1f9690a82726b4c"}, - {file = "jiter-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47455245307e4debf2ce6c6e65a717550a0244231240dcf3b8f7d64e4c2f22f4"}, - {file = "jiter-0.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9da221dca6e0429c2704c1b3655fe7b025204a71d4d9b73390c759d776d165"}, - {file = "jiter-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ab43126d5e05f3d53a36a8e11eb2f23304c6c1117844aaaf9a0aa5e40b5018"}, - {file = "jiter-0.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9da38b4fedde4fb528c740c2564628fbab737166a0e73d6d46cb4bb5463ff411"}, - {file = "jiter-0.13.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b34c519e17658ed88d5047999a93547f8889f3c1824120c26ad6be5f27b6cf5"}, - {file = "jiter-0.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2a6394e6af690d462310a86b53c47ad75ac8c21dc79f120714ea449979cb1d3"}, - {file = "jiter-0.13.0-cp311-cp311-win32.whl", hash = "sha256:0f0c065695f616a27c920a56ad0d4fc46415ef8b806bf8fc1cacf25002bd24e1"}, - {file = "jiter-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0733312953b909688ae3c2d58d043aa040f9f1a6a75693defed7bc2cc4bf2654"}, - {file = "jiter-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:5d9b34ad56761b3bf0fbe8f7e55468704107608512350962d3317ffd7a4382d5"}, - {file = "jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663"}, - {file = "jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505"}, - {file = "jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152"}, - {file = "jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726"}, - {file = "jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0"}, - {file = "jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089"}, - {file = "jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93"}, - {file = "jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08"}, - {file = "jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2"}, - {file = "jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228"}, - {file = "jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394"}, - {file = "jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92"}, - {file = "jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9"}, - {file = "jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf"}, - {file = "jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a"}, - {file = "jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb"}, - {file = "jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2"}, - {file = "jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f"}, - {file = "jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159"}, - {file = "jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663"}, - {file = "jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa"}, - {file = "jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820"}, - {file = "jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68"}, - {file = "jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72"}, - {file = "jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc"}, - {file = "jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b"}, - {file = "jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10"}, - {file = "jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef"}, - {file = "jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6"}, - {file = "jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d"}, - {file = "jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d"}, - {file = "jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0"}, - {file = "jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91"}, - {file = "jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09"}, - {file = "jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607"}, - {file = "jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66"}, - {file = "jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2"}, - {file = "jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad"}, - {file = "jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d"}, - {file = "jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df"}, - {file = "jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d"}, - {file = "jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6"}, - {file = "jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f"}, - {file = "jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d"}, - {file = "jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0"}, - {file = "jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40"}, - {file = "jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202"}, - {file = "jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0"}, - {file = "jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95"}, - {file = "jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59"}, - {file = "jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe"}, - {file = "jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939"}, - {file = "jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9"}, - {file = "jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6"}, - {file = "jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8"}, - {file = "jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024"}, - {file = "jiter-0.13.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:4397ee562b9f69d283e5674445551b47a5e8076fdde75e71bfac5891113dc543"}, - {file = "jiter-0.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f90023f8f672e13ea1819507d2d21b9d2d1c18920a3b3a5f1541955a85b5504"}, - {file = "jiter-0.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed0240dd1536a98c3ab55e929c60dfff7c899fecafcb7d01161b21a99fc8c363"}, - {file = "jiter-0.13.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6207fc61c395b26fffdcf637a0b06b4326f35bfa93c6e92fe1a166a21aeb6731"}, - {file = "jiter-0.13.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00203f47c214156df427b5989de74cb340c65c8180d09be1bf9de81d0abad599"}, - {file = "jiter-0.13.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c26ad6967c9dcedf10c995a21539c3aa57d4abad7001b7a84f621a263a6b605"}, - {file = "jiter-0.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a576f5dce9ac7de5d350b8e2f552cf364f32975ed84717c35379a51c7cb198bd"}, - {file = "jiter-0.13.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b22945be8425d161f2e536cdae66da300b6b000f1c0ba3ddf237d1bfd45d21b8"}, - {file = "jiter-0.13.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6eeb7db8bc77dc20476bc2f7407a23dbe3d46d9cc664b166e3d474e1c1de4baa"}, - {file = "jiter-0.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:19cd6f85e1dc090277c3ce90a5b7d96f32127681d825e71c9dce28788e39fc0c"}, - {file = "jiter-0.13.0-cp39-cp39-win32.whl", hash = "sha256:dc3ce84cfd4fa9628fe62c4f85d0d597a4627d4242cfafac32a12cc1455d00f7"}, - {file = "jiter-0.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:9ffda299e417dc83362963966c50cb76d42da673ee140de8a8ac762d4bb2378b"}, - {file = "jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b1cbfa133241d0e6bdab48dcdc2604e8ba81512f6bbd68ec3e8e1357dd3c316c"}, - {file = "jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:db367d8be9fad6e8ebbac4a7578b7af562e506211036cba2c06c3b998603c3d2"}, - {file = "jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f6f8efb2f3b0603092401dc2df79fa89ccbc027aaba4174d2d4133ed661434"}, - {file = "jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:597245258e6ad085d064780abfb23a284d418d3e61c57362d9449c6c7317ee2d"}, - {file = "jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a"}, - {file = "jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f"}, - {file = "jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59"}, - {file = "jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19"}, - {file = "jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4"}, + {file = "jiter-0.16.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fc4f8def331036a7b8e981b4347ebe409981edbc8308a5ea842b8c3614fa6c"}, + {file = "jiter-0.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a71d0d2014c3275043e1170bf3d4e771493cb0dcf07be54c567155f4d8ee64b"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:741eed508c233a76313a1c7b001f8f21b82f14327e9196ae8bd29a2cc164ae84"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3fb7bc819187b56dc48aa5c833aaf92257da8e07efdb9306156667bd2eeb491c"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c9610fd25ebccb43fca584136f5c2fbb26802447eccd430dfdbab95a0fd5126"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4a1d68ff7ca1d3b5dee20a97a3decda7d5f15003823bf6d140c81f8561d3bc5c"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb08c276dd02dac3a284acdd02cacc630d2e3cd6572a4b85519f35cbd133c3de"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:8fc4d94713c4697347e38faf7d6ef91547c142219bdcfc7220c4870879974244"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0f05e229edb29e68cdd0ccb83cea13b64263416120cf943767a6fd72e6787f"}, + {file = "jiter-0.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2c842cbf374a8daf50b2c04212995bee34ca2ac2cdc29a901b4cdb072c9c4131"}, + {file = "jiter-0.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5ed466aee31294d7cdcd4d37dfe5c42c97bc29d9a5f00eacf24504358309cb9b"}, + {file = "jiter-0.16.0-cp310-cp310-win32.whl", hash = "sha256:b42e9ff5376819c053da25809a8d4b6fa6e473b4856ebe42e298ac958be3d7f9"}, + {file = "jiter-0.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:10438939205546132189c8e74a2d536a707841f3a25cd7c74ee91fe503407a26"}, + {file = "jiter-0.16.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:67fddeda1688f0cce2d2ae83ccf8a80f79936f2d2997d6cc2261f82fdb54a4d3"}, + {file = "jiter-0.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c90c0f63df322be920eda6ce622e3083d8906ba267f8220fe7873213b8b4430e"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64c0203212098470032aabcde9356fc168f377aade3e43def61dfe17e92f2037"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12288303c9844e61e1651d02a9a6f6633e47d39f897d6991d1427161ce6b746e"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cf109d010b4b05a105afb3d43be36a21322d345ad3111e13d15f680afef0e5b"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62c1b7fe1f77925acf5af68b6140b8810fa87dfd4dc0a9c8568ec2fa2a10429c"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8597d23c87f59294f83bcb6229b9ed1fccee13dbba967b46930d2f1759466fee"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3126a5dbad56401989ac769aca0cb56005bfb3e2366eea0ca99d1a91c3c1ee03"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c4b4717bdb35ae456f831a6b08d01880fff399887a6bbc526a583a406e484eea"}, + {file = "jiter-0.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:adff21bc78edfe086c15eb495b900306076de378dc2337c132401fc39bd79c91"}, + {file = "jiter-0.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dab907db06fc593645e73109acf4581ba5b548897d28b9348dc41ddc8343b2d3"}, + {file = "jiter-0.16.0-cp311-cp311-win32.whl", hash = "sha256:560b2cf3fb03240cd34f27409a238547488708f05b7c3924f571a60422251ec7"}, + {file = "jiter-0.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:e431cfc9caf44c1d5459ff77d4e64cbf85fddb6a35dad836a15c6a9ec23087c1"}, + {file = "jiter-0.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:2a8e9e39cf083016137aa5cadafe3188adc2ba6ba1fbf1e5d18889ad3e9ad056"}, + {file = "jiter-0.16.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:67c3bc1760f8c99d805dcab4e644027142a53b1d5d861f18780ebdbd5d40b72a"}, + {file = "jiter-0.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5af7780e4a26bd7d0d989592bf9ef12ebf806b74ab709223ecca37c749872ea9"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5bf78d0e05e45cfdd66558893938d59afe3d1b1a824a202039b20e607d25a72"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4444a83f946605990c98f625cdd3d2725bfb818158760c5748c653170a20e0e"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a23f0e4f957e1be65752d2dfac9a5a06b1917af8dc85deb639c3b9d02e31290"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c22a488f7b9218e245a0025a9ba6b100e2e54700831cf4cf16833a27fba3ad01"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46add52f4ad47a08bfb1219f3e673da972191489a33016edefdb5ea55bfa8c48"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9c8a956fd72c2cf1e730d01ea080341f13aa0a97a4a33b51abebe725b7ae9ca9"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:561926e0573ffe4a32498420a76d64b16c513e1ab413b9d28158a8764ac701e5"}, + {file = "jiter-0.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:44d019fa8cdaf89bf29c71b39e3712143fdd0ac76725c6ef954f9957a5ea8730"}, + {file = "jiter-0.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0df91907609837f33341b8e6fe73b95991fdaa57caf1a0fbd343dffe826f386f"}, + {file = "jiter-0.16.0-cp312-cp312-win32.whl", hash = "sha256:51d7b836acb0108d7c77df1742332cac2a1fa04a74d6dacec46e7091f0e91274"}, + {file = "jiter-0.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1878349266f8ee36ecb1375cc5ba2f115f35fd9f0a1a4119e725e379126647f7"}, + {file = "jiter-0.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:2ed5738ae4af18271a51a528b8811b0cbfa4a1858de9d83359e4169855d6a331"}, + {file = "jiter-0.16.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:41977aa5654023948c2dae2a81cbf9c43343954bef1cd59a154dd15a4d84c195"}, + {file = "jiter-0.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d28bb3c26762358dadf3e5bf0bccd29ae987d65e6988d2e6f49829c76b003c09"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0542a7189c26920778658fc8fcf2af8bae05bae9924577f71804acef37996536"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fb8de1e23a0cb2a7f53c335049c7b72b6db41aa6227cdcc0972a1de5cb39450"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b72d0b2990ca754a9102779ac98d8597b7cb31678958562214a007f909eab78e"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f91b1c27fc22a57993d5a5cb8a627cb8ed4b10502716fac1ffbfe1d19d84e8"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c682bea068a90b764577bdb78a60a4c1d1606daf9cd4c893832a37c7cc9d9026"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:8d031aabecc4f1b6276adfb42e3aabb77c89d468bf616600e8d3a11328929053"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eab2cd170150e70153de16896a1774e3a1dca80154c56b54d7a812c479a7165e"}, + {file = "jiter-0.16.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6edb63a46e65a82c26800a868e49b2cac30dd5a4218b88d74bc2c848c8ad60bb"}, + {file = "jiter-0.16.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:659039cc50b5addcc35fcc87ae2c1833b7c0a8e5326ef631a75e4478447bcf84"}, + {file = "jiter-0.16.0-cp313-cp313-win32.whl", hash = "sha256:c9c53be232c2e206ef9cdbad81a48bfa74c3d3f08bcf8124630a8a748aad993e"}, + {file = "jiter-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:baad945ed47f163ad833314f8e3288c396118934f94e7bbb9e243ce4b341a4fd"}, + {file = "jiter-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:3c1fd2dbe1b0af19e987f03fe66c5f5bd105a2229c1aff4ab14890b24f41d21a"}, + {file = "jiter-0.16.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b2c61484666ad42726029af0c00ef4541f0f3b5cdc550221f56c2343208018ee"}, + {file = "jiter-0.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63efadc657488f45db1c676d81e704cac2abf3fdb892def1faea61db053127e2"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf0d73f50e7b6935677854f6e8e31d499ca7064dd24734f703e060f5b237d883"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3ea07d9bc8e7d03a9fbc051295462e6dbc295b894fd72457c3136e3e43d898"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26798522707abb47d767db536e4148ceac1b14446bf028ee85e579a2e043cfe5"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc837c1b9631be10abfe0191537fe8009838204cec7e44827401ace390ddb567"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49060fd70737fad59d33ba9dcc0d83247dc9e77187de26053a19c16c9f32bd69"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:adbb8edeadd431bc4477879d5d371ece7cb1334486584e0f252656dd7ffada29"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:31aaee5b80f672c1dc21272bcfb9cbdcfc1ea04ff50f00ed5af500b80c44fa93"}, + {file = "jiter-0.16.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6722bcef4ffc86c835574b1b2fac6b33b9fb4a889c781e67950e891591f3c55a"}, + {file = "jiter-0.16.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:5ab4f50ff971b611d656554ea10b75f80097392c827bc32923c6eeb6386c8b00"}, + {file = "jiter-0.16.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:710cc51d4ebdcd3c1f70b232c1db1ea1344a075770422bbd4bede5708335acbe"}, + {file = "jiter-0.16.0-cp314-cp314-win32.whl", hash = "sha256:57b37fc887a32d44798e4d8ebfa7c9683ff3da1d5bf38f08d1bb3573ccb39106"}, + {file = "jiter-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbd18dd5e2df96b580487b5745adf57ef64ad89ba2d9662fc3c19386acce7db8"}, + {file = "jiter-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:a32d2027a9fa67f109ff245a3252ece3ccc32cc56703e1deab6cc846a59e0585"}, + {file = "jiter-0.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2577196f4474ef3fc4779a088a23b0897bbf86f9ea3679c372d45b8383b43207"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e89e008a93c01104161c75b4988e58716b01d62307ebfe161e52a56d2a818"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2e9efbe042210df657bade597f66d6d75723e3d8f45a12ea6d8167ff8bbce3"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f4d9e473a5ce7d27fef8b848df4dc16e283893d3f53b4a585e72c9595f3c284"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d30a4a1c87713060c8d1cc59a7b6c8fb6b8ef0a6900368014c76c87922a2929"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae96332410f866e5900d809298b1ed82735932986c672495f9701daacd80620"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:da3d7ec75dc83bb18bca888b5edfae0656a26849056c59e05a7728badd17e7af"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ee6162b77d49a9939229df666dfa8af3e656b6701b54c4c84966d740e189264e"}, + {file = "jiter-0.16.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:63ffdbdae7d4499f4cda14eadc12ddcabef0fc0c081191bdc2247489cb698077"}, + {file = "jiter-0.16.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a111256a7193bea0759267b10385e5870949c239ed7b6ddbaaf57573edb38734"}, + {file = "jiter-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:de5ba8763e56b793561f43bed197c9ea55776daa5e9a6b91eed68a909bc9cdbf"}, + {file = "jiter-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b8a3f9a6008048fe9def7bf465180564a6e458047d2ce499149cfbe73c3ae9db"}, + {file = "jiter-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0fa25b09b13075c46f5bc174f2690525a925a4fc2f7c82969a2bbabff22386ce"}, + {file = "jiter-0.16.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d8f80521644426d451e70f00c7974240cab8f6ee088aedaa9af2697153ab7805"}, + {file = "jiter-0.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3b21b412b899fd8bd51a3046934b59a3bb068b79f70a5c6010053ac77cc53f0c"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0758ab7747a984797cf048e8eedea1d8ef39d7994b25611daf5b48fc903e8873"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ec553a99b0987efd7a3645a1a825cf29c224e494db267a83369fcc8da9aeda5"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3bd327cdfa118bc1ce69c214c2678571d5bd39b8ccd0ebf43a54db00541ba9a"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26d122613ada2b708eb714695446f40fce5bdf2edb4b02116dec62faa62dfab3"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e03a5f21a5ce96a9441b8cb32719a8b88ed5388f53e0f339c5bcf54f1317f9d0"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:a5c54ef4ff776d9675837ef535b3308d6e31c208d43ebc44a0f7ab8a208c68f7"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1e7923093a376d93c6eb507c77045ae258d689ba577392846a1b3f10d0b09a9"}, + {file = "jiter-0.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2a0d46ef67cc58d906a6132dd3040ca70ae4f0b0d7c9c052fe432c658a69b3f6"}, + {file = "jiter-0.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:70a490b55634dc0d2606ce8a8e01b1d62459011beb368d15d76e1eaf62460e3d"}, + {file = "jiter-0.16.0-cp39-cp39-win32.whl", hash = "sha256:9acf1b2faec82d998811ecce7ae84d9005e53410773e9d37d61cdc424ba4581b"}, + {file = "jiter-0.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:491e7d072a253b156fff46b78bceac4652a697aa8d7082c9c18c03d7b7917d24"}, + {file = "jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:850ccb1d7eedb4200f4014b1c0e8a577de114fc3cd88faad646dcc9bc4bb12ad"}, + {file = "jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:e34e97bda77eb63242a410243c071e28ac7e0d8c0948c5ee658498690a4b2f2f"}, + {file = "jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7dc85ea77d4abbae8bad0d3538678aedee75bceec4e2f6c8dfb1c74772e5aa5"}, + {file = "jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17ca7fae79f6d99cd9a042b75f917eaada7b895cfc7dd2ee3a16089dcaec7a85"}, + {file = "jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:f17d61a28b4b3e0e3e2ba98490c70501403b4d196f78732439160e7fd3678127"}, + {file = "jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:96e38eea538c8ddf853a35727c7be0741c76c13f04148ac5c116222f50ece3b3"}, + {file = "jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d284fb8d94d5855d60c44fefcab4bf966f1da6fada73992b01f6f0c9bc0c6702"}, + {file = "jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d613743df53199b1aa256a7d328340da6d7078aac7705a7db9d7a791e9cfd2"}, + {file = "jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c"}, ] [[package]] @@ -1649,15 +1680,18 @@ files = [ [[package]] name = "json-repair" -version = "0.55.2" +version = "0.63.2" description = "A package to repair broken json strings" optional = false python-versions = ">=3.10" files = [ - {file = "json_repair-0.55.2-py3-none-any.whl", hash = "sha256:08109b093fc9fe99b956b8309f9c1dfd2740637056d93df489cc7b4eb56d4286"}, - {file = "json_repair-0.55.2.tar.gz", hash = "sha256:aa5c89692126257efb8e3cf1efe6787387634b5c360fb77313d66f146e980de6"}, + {file = "json_repair-0.63.2-py3-none-any.whl", hash = "sha256:7354c2dd433bf15dedf98d2932ae9cd1ea197d7edc77e6c0538f08e34616580e"}, + {file = "json_repair-0.63.2.tar.gz", hash = "sha256:8385ca04afbf411eebd9f0ba064155d1afe37442aaad52f693825c65cc314586"}, ] +[package.extras] +schema = ["jsonschema (>=4.21)", "pydantic (>=2)"] + [[package]] name = "jsonschema" version = "4.26.0" @@ -1695,271 +1729,338 @@ referencing = ">=0.31.0" [[package]] name = "librt" -version = "0.7.8" +version = "0.15.0" description = "Mypyc runtime library" optional = false python-versions = ">=3.9" files = [ - {file = "librt-0.7.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b45306a1fc5f53c9330fbee134d8b3227fe5da2ab09813b892790400aa49352d"}, - {file = "librt-0.7.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:864c4b7083eeee250ed55135d2127b260d7eb4b5e953a9e5df09c852e327961b"}, - {file = "librt-0.7.8-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6938cc2de153bc927ed8d71c7d2f2ae01b4e96359126c602721340eb7ce1a92d"}, - {file = "librt-0.7.8-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66daa6ac5de4288a5bbfbe55b4caa7bf0cd26b3269c7a476ffe8ce45f837f87d"}, - {file = "librt-0.7.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4864045f49dc9c974dadb942ac56a74cd0479a2aafa51ce272c490a82322ea3c"}, - {file = "librt-0.7.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a36515b1328dc5b3ffce79fe204985ca8572525452eacabee2166f44bb387b2c"}, - {file = "librt-0.7.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b7e7f140c5169798f90b80d6e607ed2ba5059784968a004107c88ad61fb3641d"}, - {file = "librt-0.7.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff71447cb778a4f772ddc4ce360e6ba9c95527ed84a52096bd1bbf9fee2ec7c0"}, - {file = "librt-0.7.8-cp310-cp310-win32.whl", hash = "sha256:047164e5f68b7a8ebdf9fae91a3c2161d3192418aadd61ddd3a86a56cbe3dc85"}, - {file = "librt-0.7.8-cp310-cp310-win_amd64.whl", hash = "sha256:d6f254d096d84156a46a84861183c183d30734e52383602443292644d895047c"}, - {file = "librt-0.7.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3e9c11aa260c31493d4b3197d1e28dd07768594a4f92bec4506849d736248f"}, - {file = "librt-0.7.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddb52499d0b3ed4aa88746aaf6f36a08314677d5c346234c3987ddc506404eac"}, - {file = "librt-0.7.8-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e9c0afebbe6ce177ae8edba0c7c4d626f2a0fc12c33bb993d163817c41a7a05c"}, - {file = "librt-0.7.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:631599598e2c76ded400c0a8722dec09217c89ff64dc54b060f598ed68e7d2a8"}, - {file = "librt-0.7.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c1ba843ae20db09b9d5c80475376168feb2640ce91cd9906414f23cc267a1ff"}, - {file = "librt-0.7.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b5b007bb22ea4b255d3ee39dfd06d12534de2fcc3438567d9f48cdaf67ae1ae3"}, - {file = "librt-0.7.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbd79caaf77a3f590cbe32dc2447f718772d6eea59656a7dcb9311161b10fa75"}, - {file = "librt-0.7.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:87808a8d1e0bd62a01cafc41f0fd6818b5a5d0ca0d8a55326a81643cdda8f873"}, - {file = "librt-0.7.8-cp311-cp311-win32.whl", hash = "sha256:31724b93baa91512bd0a376e7cf0b59d8b631ee17923b1218a65456fa9bda2e7"}, - {file = "librt-0.7.8-cp311-cp311-win_amd64.whl", hash = "sha256:978e8b5f13e52cf23a9e80f3286d7546baa70bc4ef35b51d97a709d0b28e537c"}, - {file = "librt-0.7.8-cp311-cp311-win_arm64.whl", hash = "sha256:20e3946863d872f7cabf7f77c6c9d370b8b3d74333d3a32471c50d3a86c0a232"}, - {file = "librt-0.7.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b6943885b2d49c48d0cff23b16be830ba46b0152d98f62de49e735c6e655a63"}, - {file = "librt-0.7.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46ef1f4b9b6cc364b11eea0ecc0897314447a66029ee1e55859acb3dd8757c93"}, - {file = "librt-0.7.8-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:907ad09cfab21e3c86e8f1f87858f7049d1097f77196959c033612f532b4e592"}, - {file = "librt-0.7.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2991b6c3775383752b3ca0204842743256f3ad3deeb1d0adc227d56b78a9a850"}, - {file = "librt-0.7.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03679b9856932b8c8f674e87aa3c55ea11c9274301f76ae8dc4d281bda55cf62"}, - {file = "librt-0.7.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3968762fec1b2ad34ce57458b6de25dbb4142713e9ca6279a0d352fa4e9f452b"}, - {file = "librt-0.7.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bb7a7807523a31f03061288cc4ffc065d684c39db7644c676b47d89553c0d714"}, - {file = "librt-0.7.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad64a14b1e56e702e19b24aae108f18ad1bf7777f3af5fcd39f87d0c5a814449"}, - {file = "librt-0.7.8-cp312-cp312-win32.whl", hash = "sha256:0241a6ed65e6666236ea78203a73d800dbed896cf12ae25d026d75dc1fcd1dac"}, - {file = "librt-0.7.8-cp312-cp312-win_amd64.whl", hash = "sha256:6db5faf064b5bab9675c32a873436b31e01d66ca6984c6f7f92621656033a708"}, - {file = "librt-0.7.8-cp312-cp312-win_arm64.whl", hash = "sha256:57175aa93f804d2c08d2edb7213e09276bd49097611aefc37e3fa38d1fb99ad0"}, - {file = "librt-0.7.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c3995abbbb60b3c129490fa985dfe6cac11d88fc3c36eeb4fb1449efbbb04fc"}, - {file = "librt-0.7.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44e0c2cbc9bebd074cf2cdbe472ca185e824be4e74b1c63a8e934cea674bebf2"}, - {file = "librt-0.7.8-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d2f1e492cae964b3463a03dc77a7fe8742f7855d7258c7643f0ee32b6651dd3"}, - {file = "librt-0.7.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:451e7ffcef8f785831fdb791bd69211f47e95dc4c6ddff68e589058806f044c6"}, - {file = "librt-0.7.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3469e1af9f1380e093ae06bedcbdd11e407ac0b303a56bbe9afb1d6824d4982d"}, - {file = "librt-0.7.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f11b300027ce19a34f6d24ebb0a25fd0e24a9d53353225a5c1e6cadbf2916b2e"}, - {file = "librt-0.7.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4adc73614f0d3c97874f02f2c7fd2a27854e7e24ad532ea6b965459c5b757eca"}, - {file = "librt-0.7.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60c299e555f87e4c01b2eca085dfccda1dde87f5a604bb45c2906b8305819a93"}, - {file = "librt-0.7.8-cp313-cp313-win32.whl", hash = "sha256:b09c52ed43a461994716082ee7d87618096851319bf695d57ec123f2ab708951"}, - {file = "librt-0.7.8-cp313-cp313-win_amd64.whl", hash = "sha256:f8f4a901a3fa28969d6e4519deceab56c55a09d691ea7b12ca830e2fa3461e34"}, - {file = "librt-0.7.8-cp313-cp313-win_arm64.whl", hash = "sha256:43d4e71b50763fcdcf64725ac680d8cfa1706c928b844794a7aa0fa9ac8e5f09"}, - {file = "librt-0.7.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:be927c3c94c74b05128089a955fba86501c3b544d1d300282cc1b4bd370cb418"}, - {file = "librt-0.7.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7b0803e9008c62a7ef79058233db7ff6f37a9933b8f2573c05b07ddafa226611"}, - {file = "librt-0.7.8-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:79feb4d00b2a4e0e05c9c56df707934f41fcb5fe53fd9efb7549068d0495b758"}, - {file = "librt-0.7.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9122094e3f24aa759c38f46bd8863433820654927370250f460ae75488b66ea"}, - {file = "librt-0.7.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e03bea66af33c95ce3addf87a9bf1fcad8d33e757bc479957ddbc0e4f7207ac"}, - {file = "librt-0.7.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1ade7f31675db00b514b98f9ab9a7698c7282dad4be7492589109471852d398"}, - {file = "librt-0.7.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a14229ac62adcf1b90a15992f1ab9c69ae8b99ffb23cb64a90878a6e8a2f5b81"}, - {file = "librt-0.7.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5bcaaf624fd24e6a0cb14beac37677f90793a96864c67c064a91458611446e83"}, - {file = "librt-0.7.8-cp314-cp314-win32.whl", hash = "sha256:7aa7d5457b6c542ecaed79cec4ad98534373c9757383973e638ccced0f11f46d"}, - {file = "librt-0.7.8-cp314-cp314-win_amd64.whl", hash = "sha256:3d1322800771bee4a91f3b4bd4e49abc7d35e65166821086e5afd1e6c0d9be44"}, - {file = "librt-0.7.8-cp314-cp314-win_arm64.whl", hash = "sha256:5363427bc6a8c3b1719f8f3845ea53553d301382928a86e8fab7984426949bce"}, - {file = "librt-0.7.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ca916919793a77e4a98d4a1701e345d337ce53be4a16620f063191f7322ac80f"}, - {file = "librt-0.7.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:54feb7b4f2f6706bb82325e836a01be805770443e2400f706e824e91f6441dde"}, - {file = "librt-0.7.8-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:39a4c76fee41007070f872b648cc2f711f9abf9a13d0c7162478043377b52c8e"}, - {file = "librt-0.7.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac9c8a458245c7de80bc1b9765b177055efff5803f08e548dd4bb9ab9a8d789b"}, - {file = "librt-0.7.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b67aa7eff150f075fda09d11f6bfb26edffd300f6ab1666759547581e8f666"}, - {file = "librt-0.7.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:535929b6eff670c593c34ff435d5440c3096f20fa72d63444608a5aef64dd581"}, - {file = "librt-0.7.8-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:63937bd0f4d1cb56653dc7ae900d6c52c41f0015e25aaf9902481ee79943b33a"}, - {file = "librt-0.7.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf243da9e42d914036fd362ac3fa77d80a41cadcd11ad789b1b5eec4daaf67ca"}, - {file = "librt-0.7.8-cp314-cp314t-win32.whl", hash = "sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365"}, - {file = "librt-0.7.8-cp314-cp314t-win_amd64.whl", hash = "sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32"}, - {file = "librt-0.7.8-cp314-cp314t-win_arm64.whl", hash = "sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06"}, - {file = "librt-0.7.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c7e8f88f79308d86d8f39c491773cbb533d6cb7fa6476f35d711076ee04fceb6"}, - {file = "librt-0.7.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:389bd25a0db916e1d6bcb014f11aa9676cedaa485e9ec3752dfe19f196fd377b"}, - {file = "librt-0.7.8-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73fd300f501a052f2ba52ede721232212f3b06503fa12665408ecfc9d8fd149c"}, - {file = "librt-0.7.8-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d772edc6a5f7835635c7562f6688e031f0b97e31d538412a852c49c9a6c92d5"}, - {file = "librt-0.7.8-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde8a130bd0f239e45503ab39fab239ace094d63ee1d6b67c25a63d741c0f71"}, - {file = "librt-0.7.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fdec6e2368ae4f796fc72fad7fd4bd1753715187e6d870932b0904609e7c878e"}, - {file = "librt-0.7.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:00105e7d541a8f2ee5be52caacea98a005e0478cfe78c8080fbb7b5d2b340c63"}, - {file = "librt-0.7.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c6f8947d3dfd7f91066c5b4385812c18be26c9d5a99ca56667547f2c39149d94"}, - {file = "librt-0.7.8-cp39-cp39-win32.whl", hash = "sha256:41d7bb1e07916aeb12ae4a44e3025db3691c4149ab788d0315781b4d29b86afb"}, - {file = "librt-0.7.8-cp39-cp39-win_amd64.whl", hash = "sha256:e90a8e237753c83b8e484d478d9a996dc5e39fd5bd4c6ce32563bc8123f132be"}, - {file = "librt-0.7.8.tar.gz", hash = "sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862"}, + {file = "librt-0.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1a49adf16a7c9d9646816c2946135527197b6fcf4347c7b8b761cf1bfbf4489"}, + {file = "librt-0.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81a398f45b45a59200e13cd5ad1ae1d3f44334de98b148331afe2cdfee701c52"}, + {file = "librt-0.15.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4eafbaff06b9563f8b1c850621ce51605de05208e09d4d71ce490bc972b7b9e8"}, + {file = "librt-0.15.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:b0411b4066db926b80258c60dcb0e6db4c9cee312eab45b7e8866b17ddf9ada1"}, + {file = "librt-0.15.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:febb1ce6cac545a54e6b769982824e955a700fdd9fbf3a08a3d82c990968b57d"}, + {file = "librt-0.15.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b230acc1c3bfe2d6f2627ba2b95dc92e58aa494600e9722d0e6ccbc931e59702"}, + {file = "librt-0.15.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6da110e5f314c19ab8478464d02ae18808ae73d522c15260fa4918acdcd64da9"}, + {file = "librt-0.15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:eab9208b00ca55bf75983ec99f7bf13acc746a36102e98953addaad7f7ea1e1b"}, + {file = "librt-0.15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6c013cd3a1721e69e14380ada97eaa4b7b0cdf1c6b96fa765d4ea47c875088db"}, + {file = "librt-0.15.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:567b1c430f8bd560e689421468278ac5941bab4a05303b5d95b6ae10db03f451"}, + {file = "librt-0.15.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:29c4cab9df457b19672c39be7f384ebb2bc925c4e2684b8780c222b43eb36389"}, + {file = "librt-0.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bccbd8e5b0bffb7106cf18eb1baa3d7194b1cebb3b4b1cdbd4bdb19382a6ee6c"}, + {file = "librt-0.15.0-cp310-cp310-win32.whl", hash = "sha256:8ae493ed5f659a7761c43d42f183db514536073ded9bcf671d2d1df47e29a07e"}, + {file = "librt-0.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:bc25fb356d0c7810bb49ff3df908ad1fda6995d660ab099ded69244ed7ab6053"}, + {file = "librt-0.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:823b92cf3c18ecd08afc70c42473888b41b6e8ef5046f3b82c05c154a2fa3d22"}, + {file = "librt-0.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c70bc1b602cf59917e8f0c7a2cbc8bcc6fbc14d5486136b00707a79619121d63"}, + {file = "librt-0.15.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:814ff83a25b5fce8b9c80c4dd803153fb5c5599fc74db9e022466938368957ef"}, + {file = "librt-0.15.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:57f5eeb6ad4c180de583b1038e61fe5fbd9796bb69a8a1c1a0c7ddbec4c8c60f"}, + {file = "librt-0.15.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82909c8f7eb9952656b65d3147afde4cf8e6d5a991eebc86418b5e65843b0ab8"}, + {file = "librt-0.15.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f779070399f991400fc451719e0ea388eb7de313388bada2c127a35de05f798a"}, + {file = "librt-0.15.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bac89069bc496ebdf4f79ebb57bbd10d0b214c8454225deb672d91002bd17e18"}, + {file = "librt-0.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e0d00c708fb2f5822b152429b1ac80a58dbbbc3f6c232c4d13a3f7fcf2ea5b4c"}, + {file = "librt-0.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6c6624fe268625869485553dd7cc1daf30d22558215bb2a4ff16f67a9801a31a"}, + {file = "librt-0.15.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f56b397858a23dacf35ede366ed2212fdc03a6a57a1ad36468ad6e9dc5fac091"}, + {file = "librt-0.15.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4388184646efe2054911c5b00a1077d6d1ee86a95b7e8ba96dc7850a809f3f40"}, + {file = "librt-0.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97335f59082f9fe2ce6c2a9cc6433a0114bbb6cd4d5c09dd76c95c68b9f9a8b0"}, + {file = "librt-0.15.0-cp311-cp311-win32.whl", hash = "sha256:83380ffde38062a2e9bb55d83e74474f6614665528b98a6928720fc006dfffbb"}, + {file = "librt-0.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:f75720477ee05d509a310e856cacc8d909adc182f7b91193c207bcc26d7ee6db"}, + {file = "librt-0.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:256237037a3ab001ae8d9803b2d43562a4c3aa38739843694349e4d5ebb0fd56"}, + {file = "librt-0.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e87bc679f86a99aa3b26e3c78eeb821a247c9a28eae48eaafcc32c3bf4c3bb9e"}, + {file = "librt-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71599e011ac880e8e45d46047d714871894c7d4ab6f25626f8d4f89da21f368d"}, + {file = "librt-0.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c802434092b769b1d613ed2e13fac15fbfce1934a74bd10283b03c0fae231cd1"}, + {file = "librt-0.15.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5500eeae393a184d14e1f35645962c27129d20c81afa4069e6ef826ebc2b3aaa"}, + {file = "librt-0.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ecfc32dfb46fb7b565bcd6abf9412acf978775a998273d22888a6d7953730dd"}, + {file = "librt-0.15.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cc46cfd15022e35084355478c9ac809d90b1152222706ac9a7655ec21df6fa"}, + {file = "librt-0.15.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5f51401d102c885b9ca509e62c79b1dbff286e1b9b047fde6f763780789356d"}, + {file = "librt-0.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cc30523e3f1a23fb7511cc659834a0d01a1042bb9de359bc1c131cc4ec6c9656"}, + {file = "librt-0.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:59fe030d8ae4a57e3fb7756bf35a858de74e04066fc8555c53d0af979132af81"}, + {file = "librt-0.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a6526a2a956bbb1e4ae3568c82e650fc99119c66bb011ea60715744955a2b4d"}, + {file = "librt-0.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:85ea21ec6730194d67156b0e0b5430ccb1d61f8b8b907e39b37f9812b74a13f0"}, + {file = "librt-0.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1e47b8ba865d7ede071a91a7163073bbaeb72541f1ef8a07d512c45c7b5007f2"}, + {file = "librt-0.15.0-cp312-cp312-win32.whl", hash = "sha256:a5207ec414d1c4a2a7231b2086970dc036f94293cdf338190984958a013a42f1"}, + {file = "librt-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:73b30cfa976659b3917c8f6153bdb0591c6a9ec6583599fd24a689b690622022"}, + {file = "librt-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:a54cf9e0ef47b96af580849db5471142200568ce1e02cbf416addab551369570"}, + {file = "librt-0.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:db13ca398005abcbe538deda87b686d9bd08b7001cf40c4c06b444960ae10a26"}, + {file = "librt-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa1f1995789dca3698bc550aaceb09a51bd5df0a057ff84ff15296cd1975b801"}, + {file = "librt-0.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55456ea87d8df21808446d03817be2f65e20391c1c615d9187440dff28cd08dc"}, + {file = "librt-0.15.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5a86a5a08c2235316bdb359d5dbb6ce0abfca7fac06363103e2c5af571d92f95"}, + {file = "librt-0.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e56b6a368529bed262da40ce13f8fef590db0479819cca84f16a1f01ac356d0b"}, + {file = "librt-0.15.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:234d8d394721fa0d786af15ebf1f3fb7f3ed82fd1cd0cde45c2f247b5d4281d2"}, + {file = "librt-0.15.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d8363d7accb0286ac3a0e633f396e93800dafb8150494505daf9515bbda591f3"}, + {file = "librt-0.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0f0ee3644d951f31055ad07d77d92520e84505dd7a432cc4cd501dd70ee06785"}, + {file = "librt-0.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2cfd1a81a648806e6a7717be4cc4d1bb392fa229752bf8444ba365e381e984d6"}, + {file = "librt-0.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6cd22c9da0d866558e46a041f1cc0c2bbb26b61b137b2347fa834c332e1d101"}, + {file = "librt-0.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6d5225ef8801e4ea5e482fa9b5dfb891dd9ef6f6d870f1f25d449ca2c70ac218"}, + {file = "librt-0.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d28a05796b99f749bf8794f17ba9ba1612d0076b802e9cfc62c554634e9ce3b"}, + {file = "librt-0.15.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:2067ff438048cead9d223ca5675bae2a25e520a7c3e6c1498bf9c6892d22caab"}, + {file = "librt-0.15.0-cp313-cp313-win32.whl", hash = "sha256:1cd3b721f24c206398b9e26da3c3a9c011e6e89d06f318ba8ebefc30f1003890"}, + {file = "librt-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:f395a4a9a03ac062dbe9a9f82e0c720502e590a38feee6a757bc82e9c63afbd8"}, + {file = "librt-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:0a15cb554761247d84a3ec0cbdf4078d70725384f0e4662c0fa3b26266eb60ad"}, + {file = "librt-0.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f5de7feedc56337a088eb15cd9fafa9938367362221d8cc62c642b7f94821993"}, + {file = "librt-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c0eb900c0e91f4aebe680845242e614f1864edfd44106380d0752ac29522bf8"}, + {file = "librt-0.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8c9a650a188e38bac005048cbe6342e81407782944d01934540ab75e417df21"}, + {file = "librt-0.15.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:92bfed8deec93df30286b9fe9e3b1dd17329cc076a192b4ee5ec223841d54953"}, + {file = "librt-0.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec4b19788f835711a2072f9dbe6b03b3bf32ed1f0fb30cf399bdd59d9f0c33fa"}, + {file = "librt-0.15.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4c7bacb70930f3d0a56f4ecf1be474a1f0d941b01dd73b756f3c256d42cb879"}, + {file = "librt-0.15.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e79f05e4a08b4d880342673312bbc895b56df7765605796f15902eb5367d3ae"}, + {file = "librt-0.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a417149c0cba4d50b61e992e5a15e69eaf96746609b461cc4ed168aeef6b79dd"}, + {file = "librt-0.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:da7a94d6a3411f579d72aa3e3bc5fbca7ed4549f3dbd7e5de3aa567333374285"}, + {file = "librt-0.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:856f743ae607f2c1380eccb566c0038a9fb3eabf0fc2be2704d76d9f73557239"}, + {file = "librt-0.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:779a6e7c894737e5983e7790a9c78c4000c30e23c9aada08081bdbea53b0fa60"}, + {file = "librt-0.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96bb17dbe8bab3c0954fbebfc69ed395599de75b6bbc35e3270a878e15d4dd65"}, + {file = "librt-0.15.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:7220697efaa6e5348fc3d18ee7f8563d4bfecd9872b37ffb915bfc1d08840622"}, + {file = "librt-0.15.0-cp314-cp314-win32.whl", hash = "sha256:f54598964d357b1c5ab77cf5d92f21e598fe0e23cdbe9618480807f81b4eba15"}, + {file = "librt-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ff5893a2c23d886aa9ce786de5ac6ddc74aeeaf90743682b74d920e117d2e28"}, + {file = "librt-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:3722a099730704c9a3d70c879fc0f51daec25fe5f1555672d97bc595abeafb95"}, + {file = "librt-0.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:38c0c7d4b6fc06c3324b3f9162c8391bfc4fd9dde53afe1033ce7edb48d5a714"}, + {file = "librt-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8b2fdd7ead3c995c37940a790690660d0ca006c302db26cc51933f6766866fc3"}, + {file = "librt-0.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fde98cf1fc4bac144ce23c2c4c017b924ba714509ea9334977b0b27050c837d"}, + {file = "librt-0.15.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:e3b461183c5fa7681b48560f91515f53a953122fb30c71e07abc67d7ddf58c38"}, + {file = "librt-0.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4bbcc257e3babea20a91715c361b24554ec4e8f51aa578568afc230799fe1a19"}, + {file = "librt-0.15.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b845b8d48088fad0cadc84be4b8fda63203be7e9237b71015b3925443c1f35ab"}, + {file = "librt-0.15.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b30e600e8f337b9bd7f39b86d9fdfedc73cc46e3d0f745931a23a234220bb7e2"}, + {file = "librt-0.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:64b0c8c35aa4c4ed79896359f3e0b285cbe4e610042106500da4811c322cc108"}, + {file = "librt-0.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0da0d94cb802f32a0524653e7201f2cef72d5f700a5407678f5290483d4fcd08"}, + {file = "librt-0.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4a6369168d371207339b1e50d4532b06a7121586141f82599505a3f315751d47"}, + {file = "librt-0.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c434e072557ade9cbc642d052c89d031efe47d5c9614523619d0d74a02378e81"}, + {file = "librt-0.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7eec6a42018bc1d45763b1c162d3d2bf7c3b9a1b0ed30d3e91dcba390efefcc"}, + {file = "librt-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:6912fa5e635d74529ac7cdb1bdf6ca3af4453da8d1edbe0110ee1cb4ad407ebf"}, + {file = "librt-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8e11699ed745931c395acd3621b07062e0f840efa6935aad87a64ed0995f0915"}, + {file = "librt-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5d2a91724463bfed4f573cd7a9fdc856d2e230d0c0e5a61416a93481dccd8605"}, + {file = "librt-0.15.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:8443e38dcfcfdbcf5add5118c623efd788d65ac2e25756d6251a54a06a4d0aca"}, + {file = "librt-0.15.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6d15a29033c57490cfe2069097c6fc4049e4e65ffbb749be7dc453b7c4c68965"}, + {file = "librt-0.15.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d2c05c729b589e734c09578bf5964be48a911765484840d017bbc84f49d4c4ad"}, + {file = "librt-0.15.0-cp315-cp315-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:fa60887537e1d0cd2d9982269d33a709bf54b195cd2b9364fc0a758022af5bd9"}, + {file = "librt-0.15.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d8bc24219b24c0af375718942ab75e3544b2763085f40f965be4326734ae8328"}, + {file = "librt-0.15.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86a21a7bd3fe3a419512ef424cc1c020f6771d0b29cfddff36d1635a855e63f0"}, + {file = "librt-0.15.0-cp315-cp315-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dbab647e88d90b3167b91efe7091e248653688ed4337e4f90907a722c7361bb9"}, + {file = "librt-0.15.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d8edcf6f550e918dca779c069b9e156385c60b406f99fc7641f32c52f7193659"}, + {file = "librt-0.15.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:8b62076030baa2d8b1501a46bf0e19c27a489aa90671c55665bff7887f7660b0"}, + {file = "librt-0.15.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:d00d20d1818e82a07a0ee0aa89a98b17ed7916b92441090b683719cb20a59b6d"}, + {file = "librt-0.15.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4e6ee93fc3cf848dcbf0cce2eca73d8e7dcd0cc2b6df3a529d57750b30a4c55c"}, + {file = "librt-0.15.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:32896a0af72508ea979e0acb4e4c04cbeeae04938167950d535c83c45597167d"}, + {file = "librt-0.15.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:ec3ba415afaf951f6951b1dd16d3c8e4f540065fc382d7e70b823a79567ca374"}, + {file = "librt-0.15.0-cp315-cp315-win32.whl", hash = "sha256:d2813ba2503764f0450680c533d13df7cff9b49df1411062eded5f67db4195b9"}, + {file = "librt-0.15.0-cp315-cp315-win_amd64.whl", hash = "sha256:b87d67e33afaf265262f2a66db578284b88ee2e6fcd224579cb5c15518677ad8"}, + {file = "librt-0.15.0-cp315-cp315-win_arm64.whl", hash = "sha256:713bd7df21170b982e729e46870f31d6b437bd1a9b4648cffb529bd3c2ec5c4b"}, + {file = "librt-0.15.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:3de789c82752730f94782a5ee518baf9c05edf85733aeaf73bb6e518755cdf54"}, + {file = "librt-0.15.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:e0b5deec9a8664eb722c797241970fd4aa1894d25fda36a1ddac0f7407606bd6"}, + {file = "librt-0.15.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5563302a8359bc2295bb7084d1a8ed1519df96afb30eb2aa4e0bff7b54228988"}, + {file = "librt-0.15.0-cp315-cp315t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:22d6263b9d39d7bbb286fa791945646e3218f1be2d693e36fb630f1d0e59cd13"}, + {file = "librt-0.15.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39ffd14646190c454f0d86e0d256b33f00a87a26ab410e619773b841d0e41416"}, + {file = "librt-0.15.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47318cd3a61401452de11282242937e3e057c4fd3dbaf601e269d0928a06c0a"}, + {file = "librt-0.15.0-cp315-cp315t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a56a1d4f859a82ca5b99fc4b82c9b027b15e3c455c5cd99e7d0719f27bb20b6c"}, + {file = "librt-0.15.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:077471b3182db4e17c36ae91555f36a4d2c00080b267f749bcad34a478a9a302"}, + {file = "librt-0.15.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:411ca4d1b905b860ceba7570dd6717a71dedaddcc4b0f77ece710aa41ee11f8d"}, + {file = "librt-0.15.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1256589e0b0adb31751d685a68bce29d73407ddf4ef05d4188f49d5dcf9566d9"}, + {file = "librt-0.15.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:f42b74a53e5f26a0ba0007411a7455b66c67ce4022a39cc1f56fc4efd65bcbab"}, + {file = "librt-0.15.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:291bf73caf78b9e88d6fae9bfd693207ff7d832e2fdbe2cf8e746bc13f5f892b"}, + {file = "librt-0.15.0-cp315-cp315t-win32.whl", hash = "sha256:c16d15ee371643ab48dc8248a3e680ebbeca573a13af2c3dd0c985b142d77162"}, + {file = "librt-0.15.0-cp315-cp315t-win_amd64.whl", hash = "sha256:dbd605739f228912dc49027cb764456b9757750bdc2b6b7773164db7096c6fd1"}, + {file = "librt-0.15.0-cp315-cp315t-win_arm64.whl", hash = "sha256:84d244b00604d17df3fc7736c327892d6bba66181254aa4087be807b6c342bdc"}, + {file = "librt-0.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0e2d0c0acf5b0ada7d045912b7cf787c21315c95b38b1fa939ef72d45d366b3d"}, + {file = "librt-0.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9ca190fe9edc0eb08eec558a509a16d28d91c35667b8f043cba40ed5e77a959"}, + {file = "librt-0.15.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80811e1c42386ea95c6fb30571d3250ad43d7863f883f787f70517f441150e59"}, + {file = "librt-0.15.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:88c2a17815c266e6d8180204ff62cb739ab869ada4a746d4c505331526ac58f1"}, + {file = "librt-0.15.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a5fa8f1f916988d0bf1afea005bda37f56ac41a18016e813ccf0097a8d460ca4"}, + {file = "librt-0.15.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:355e3a4c725225a14262004fc1872a552b9d3634b4f791a0dfc80804aafbfd55"}, + {file = "librt-0.15.0-cp39-cp39-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc1ed11c4ad0b91af24def2050f2840ea4567828e3dd058fbe608d982f6e5465"}, + {file = "librt-0.15.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1f4ef2e71db33df4309167ed7f1520c4fae5e611226e159fa9cf33f93e6ddb3d"}, + {file = "librt-0.15.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1a1a8cd430c7dd0c083f455cb1b328d7fc682b05c31b940906f7845bdff80881"}, + {file = "librt-0.15.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:04d5387b908676c0b8d5d2f5fb58373b4ea382d81f7a6f0fab8ea2a462bb4738"}, + {file = "librt-0.15.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:1172c6ad2a88b646e7fe3b480e3fac4ab4418b3443fd8a4061fdd531e0622fc7"}, + {file = "librt-0.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:52e8db01f603f5da0ca30987479acff98769382efc8e142fa3962395dcf3ffdb"}, + {file = "librt-0.15.0-cp39-cp39-win32.whl", hash = "sha256:e4c911f15a1652ca94ae9f1abd92e74cbb1b3597d2d92fdd556202f94e8cd455"}, + {file = "librt-0.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:68242379c9b65a582b6e97318a1e9fbd6d445e58954f2d437991c4804ab11578"}, + {file = "librt-0.15.0.tar.gz", hash = "sha256:4e66cbe84437497d951b799d3e1551291b6fb3d643820a7014b3655d57a59162"}, ] [[package]] name = "litellm" -version = "1.81.6" +version = "1.97.0" description = "Library to easily interface with LLM API providers" optional = false -python-versions = "<4.0,>=3.9" +python-versions = "<3.15,>=3.10" files = [ - {file = "litellm-1.81.6-py3-none-any.whl", hash = "sha256:573206ba194d49a1691370ba33f781671609ac77c35347f8a0411d852cf6341a"}, - {file = "litellm-1.81.6.tar.gz", hash = "sha256:f02b503dfb7d66d1c939f82e4db21aeec1d6e2ed1fe3f5cd02aaec3f792bc4ae"}, + {file = "litellm-1.97.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ff401dd5d66f54b9b474f0652c419fb7bf883fbf5ca64c0bc363acdc98b758b5"}, + {file = "litellm-1.97.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:2983b40ed5d8b1bcbbfbc0d66fefa21b04db91b87c05dd680ae77cba74e561ef"}, + {file = "litellm-1.97.0-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e3a1f70d693716b4e8a8108f0a464b0e2c555e274ddbb8ef89c4c59c80be14ed"}, + {file = "litellm-1.97.0-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5b56dce7df44a6a9e6caf5379de2578a8cb82831ceabd3d71cc99b370a1015e7"}, + {file = "litellm-1.97.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6b360ddc3162c2ed39b64d3f9957a7af70cd9c60cf71f7a4dfa355a0bf05bebc"}, + {file = "litellm-1.97.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3c4f1dd45e14127f2303769a7ae79482697823e329ae503513d014ceea4dd704"}, + {file = "litellm-1.97.0-cp310-abi3-win_amd64.whl", hash = "sha256:dce3377207234fc5c5b275a5e234ba056a5051fd178ae8e9a6aeb5d056f12095"}, + {file = "litellm-1.97.0.tar.gz", hash = "sha256:6f7ce326a2e5385ef850e0b0768d41f502ec79278860090a838511cea067b067"}, ] [package.dependencies] -aiohttp = ">=3.10" -click = "*" -fastuuid = ">=0.13.0" -httpx = ">=0.23.0" -importlib-metadata = ">=6.8.0" -jinja2 = ">=3.1.2,<4.0.0" -jsonschema = ">=4.23.0,<5.0.0" -openai = ">=2.8.0" -pydantic = ">=2.5.0,<3.0.0" -python-dotenv = ">=0.2.0" -tiktoken = ">=0.7.0" -tokenizers = "*" +aiohttp = ">=3.14.2,<4.0" +click = ">=8.0.0,<9.0" +fastuuid = ">=0.14.0,<1.0" +httpx = ">=0.28.0,<1.0" +importlib-metadata = ">=8.0.0,<9.0" +jinja2 = ">=3.1.6,<4.0" +jsonschema = ">=4.0.0,<5.0" +openai = ">=2.20.0,<3.0.0" +pydantic = ">=2.10.0,<3.0.0" +pydantic-settings = ">=2.14.1,<3.0" +python-dotenv = ">=1.0.0,<2.0" +tiktoken = ">=0.8.0,<1.0" +tokenizers = ">=0.21.0,<1.0" [package.extras] -caching = ["diskcache (>=5.6.1,<6.0.0)"] -extra-proxy = ["a2a-sdk (>=0.3.22,<0.4.0)", "azure-identity (>=1.15.0,<2.0.0)", "azure-keyvault-secrets (>=4.8.0,<5.0.0)", "google-cloud-iam (>=2.19.1,<3.0.0)", "google-cloud-kms (>=2.21.3,<3.0.0)", "prisma (==0.11.0)", "redisvl (>=0.4.1,<0.5.0)", "resend (>=0.8.0)"] -google = ["google-cloud-aiplatform (>=1.38.0)"] -grpc = ["grpcio (>=1.62.3,<1.68.dev0 || >1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0)", "grpcio (>=1.75.0)"] -mlflow = ["mlflow (>3.1.4)"] -proxy = ["PyJWT (>=2.10.1,<3.0.0)", "apscheduler (>=3.10.4,<4.0.0)", "azure-identity (>=1.15.0,<2.0.0)", "azure-storage-blob (>=12.25.1,<13.0.0)", "backoff", "boto3 (==1.40.76)", "cryptography", "fastapi (>=0.120.1)", "fastapi-sso (>=0.16.0,<0.17.0)", "gunicorn (>=23.0.0,<24.0.0)", "litellm-enterprise (==0.1.27)", "litellm-proxy-extras (==0.4.29)", "mcp (>=1.25.0,<2.0.0)", "orjson (>=3.9.7,<4.0.0)", "polars (>=1.31.0,<2.0.0)", "pynacl (>=1.5.0,<2.0.0)", "python-multipart (>=0.0.22,<0.0.23)", "pyyaml (>=6.0.1,<7.0.0)", "rich (==13.7.1)", "rq", "soundfile (>=0.12.1,<0.13.0)", "uvicorn (>=0.31.1,<0.32.0)", "uvloop (>=0.21.0,<0.22.0)", "websockets (>=15.0.1,<16.0.0)"] -semantic-router = ["semantic-router (>=0.1.12)"] -utils = ["numpydoc"] +bedrock-realtime = ["aws-sdk-bedrock-runtime (>=0.7.0,<0.8.0)"] +caching = ["diskcache (>=5.6.3,<6.0)"] +cli = ["inquirerpy (>=0.3.4,<1.0)", "pyyaml (>=6.0.3,<7.0)", "requests (>=2.32.0,<3.0)", "rich (>=13.9.4,<14.0)"] +extra-proxy = ["a2a-sdk (>=1.1.0,<2.0)", "azure-identity (>=1.25.2,<2.0)", "azure-keyvault-secrets (>=4.10.0,<5.0)", "google-cloud-iam (>=2.19.1,<3.0)", "google-cloud-kms (>=2.24.2,<3.0)", "prisma (>=0.11.0,<1.0)", "redisvl (>=0.4.1,<1.0)", "resend (>=2.23.0,<3.0)"] +google = ["google-cloud-aiplatform (>=1.133.0,<2.0)"] +grpc = ["grpcio (==1.78.0)"] +mlflow = ["mlflow (>=3.11.1,<4.0)"] +proxy = ["apscheduler (>=3.11.2,<4.0)", "azure-identity (>=1.25.2,<2.0)", "azure-storage-blob (>=12.28.0,<13.0)", "backoff (>=2.2.1,<3.0)", "boto3 (>=1.43.1,<2.0)", "cryptography (>=49.0.0,<51.0)", "expression (>=5.6.0,<6.0)", "fastapi (>=0.136.3,<1.0)", "fastapi-sso (>=0.19.0,<1.0)", "granian (>=2.7.4,<3.0)", "gunicorn (>=23.0.0,<24.0)", "hiredis (>=3.0.0,<4.0)", "inquirerpy (>=0.3.4,<1.0)", "litellm-enterprise (==0.1.54)", "litellm-proxy-extras (==0.4.84)", "mcp (>=1.28.1,<2.0)", "orjson (>=3.11.6,<4.0)", "polars (>=1.38.1,<2.0)", "pyjwt (>=2.13.0,<3.0)", "pynacl (>=1.6.2,<2.0)", "pyroscope-io (>=0.8.16,<1.0)", "python-multipart (>=0.0.27,<1.0)", "pyyaml (>=6.0.3,<7.0)", "restrictedpython (>=8.1,<9.0)", "rich (>=13.9.4,<14.0)", "rq (>=2.7.0,<3.0)", "soundfile (>=0.12.1,<1.0)", "starlette (>=1.0.1,<2.0)", "uvicorn (>=0.33.0,<1.0)", "uvloop (>=0.21.0,<1.0)", "websockets (>=15.0.1,<16.0)"] +proxy-runtime = ["anthropic[vertex] (>=0.84.0,<1.0)", "azure-ai-contentsafety (>=1.0.0,<2.0)", "azure-storage-file-datalake (>=12.20.0,<13.0)", "ddtrace (>=4.8.2,<5.0)", "detect-secrets (>=1.5.0,<2.0)", "google-cloud-aiplatform (>=1.133.0,<2.0)", "google-genai (>=1.37.0,<2.0)", "grpcio (==1.78.0)", "langfuse (>=2.59.7,<3.0)", "llm-sandbox (>=0.3.39,<1.0)", "mangum (>=0.17.0,<1.0)", "opentelemetry-api (==1.28.0)", "opentelemetry-exporter-otlp (==1.28.0)", "opentelemetry-instrumentation-fastapi (==0.49b0)", "opentelemetry-sdk (==1.28.0)", "prometheus-client (>=0.20.0,<1.0)", "pypdf (>=6.12.0,<7.0)", "sentry-sdk (>=2.21.0,<3.0)"] +saml = ["python3-saml (>=1.16.0,<2.0)"] +semantic-router = ["aurelio-sdk (>=0.0.19,<1.0)", "semantic-router (>=0.1.15,<1.0)"] +stt-nvidia-riva = ["audioread (>=3.0.1)", "numpy (>=1.26.0)", "nvidia-riva-client (>=2.15.0)", "soundfile (>=0.12.1)"] +utils = ["numpydoc (>=1.8.0,<2.0)"] [[package]] name = "lxml" -version = "6.0.2" +version = "6.1.1" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.8" files = [ - {file = "lxml-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e77dd455b9a16bbd2a5036a63ddbd479c19572af81b624e79ef422f929eef388"}, - {file = "lxml-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d444858b9f07cefff6455b983aea9a67f7462ba1f6cbe4a21e8bf6791bf2153"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f952dacaa552f3bb8834908dddd500ba7d508e6ea6eb8c52eb2d28f48ca06a31"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71695772df6acea9f3c0e59e44ba8ac50c4f125217e84aab21074a1a55e7e5c9"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17f68764f35fd78d7c4cc4ef209a184c38b65440378013d24b8aecd327c3e0c8"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:058027e261afed589eddcfe530fcc6f3402d7fd7e89bfd0532df82ebc1563dba"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8ffaeec5dfea5881d4c9d8913a32d10cfe3923495386106e4a24d45300ef79c"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:f2e3b1a6bb38de0bc713edd4d612969dd250ca8b724be8d460001a387507021c"}, - {file = "lxml-6.0.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d6690ec5ec1cce0385cb20896b16be35247ac8c2046e493d03232f1c2414d321"}, - {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2a50c3c1d11cad0ebebbac357a97b26aa79d2bcaf46f256551152aa85d3a4d1"}, - {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3efe1b21c7801ffa29a1112fab3b0f643628c30472d507f39544fd48e9549e34"}, - {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:59c45e125140b2c4b33920d21d83681940ca29f0b83f8629ea1a2196dc8cfe6a"}, - {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:452b899faa64f1805943ec1c0c9ebeaece01a1af83e130b69cdefeda180bb42c"}, - {file = "lxml-6.0.2-cp310-cp310-win32.whl", hash = "sha256:1e786a464c191ca43b133906c6903a7e4d56bef376b75d97ccbb8ec5cf1f0a4b"}, - {file = "lxml-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:dacf3c64ef3f7440e3167aa4b49aa9e0fb99e0aa4f9ff03795640bf94531bcb0"}, - {file = "lxml-6.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:45f93e6f75123f88d7f0cfd90f2d05f441b808562bf0bc01070a00f53f5028b5"}, - {file = "lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607"}, - {file = "lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178"}, - {file = "lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553"}, - {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb"}, - {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a"}, - {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c"}, - {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7"}, - {file = "lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46"}, - {file = "lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078"}, - {file = "lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285"}, - {file = "lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456"}, - {file = "lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0"}, - {file = "lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092"}, - {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f"}, - {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8"}, - {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f"}, - {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6"}, - {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322"}, - {file = "lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849"}, - {file = "lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f"}, - {file = "lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6"}, - {file = "lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77"}, - {file = "lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6"}, - {file = "lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a"}, - {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679"}, - {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659"}, - {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484"}, - {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2"}, - {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314"}, - {file = "lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2"}, - {file = "lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7"}, - {file = "lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf"}, - {file = "lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe"}, - {file = "lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37"}, - {file = "lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9"}, - {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917"}, - {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f"}, - {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8"}, - {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a"}, - {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c"}, - {file = "lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b"}, - {file = "lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed"}, - {file = "lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8"}, - {file = "lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d"}, - {file = "lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d"}, - {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9"}, - {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e"}, - {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d"}, - {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec"}, - {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272"}, - {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f"}, - {file = "lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312"}, - {file = "lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca"}, - {file = "lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c"}, - {file = "lxml-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a656ca105115f6b766bba324f23a67914d9c728dafec57638e2b92a9dcd76c62"}, - {file = "lxml-6.0.2-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c54d83a2188a10ebdba573f16bd97135d06c9ef60c3dc495315c7a28c80a263f"}, - {file = "lxml-6.0.2-cp38-cp38-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:1ea99340b3c729beea786f78c38f60f4795622f36e305d9c9be402201efdc3b7"}, - {file = "lxml-6.0.2-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:af85529ae8d2a453feee4c780d9406a5e3b17cee0dd75c18bd31adcd584debc3"}, - {file = "lxml-6.0.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fe659f6b5d10fb5a17f00a50eb903eb277a71ee35df4615db573c069bcf967ac"}, - {file = "lxml-6.0.2-cp38-cp38-win32.whl", hash = "sha256:5921d924aa5468c939d95c9814fa9f9b5935a6ff4e679e26aaf2951f74043512"}, - {file = "lxml-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:0aa7070978f893954008ab73bb9e3c24a7c56c054e00566a21b553dc18105fca"}, - {file = "lxml-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2c8458c2cdd29589a8367c09c8f030f1d202be673f0ca224ec18590b3b9fb694"}, - {file = "lxml-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fee0851639d06276e6b387f1c190eb9d7f06f7f53514e966b26bae46481ec90"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b2142a376b40b6736dfc214fd2902409e9e3857eff554fed2d3c60f097e62a62"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6b5b39cc7e2998f968f05309e666103b53e2edd01df8dc51b90d734c0825444"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4aec24d6b72ee457ec665344a29acb2d35937d5192faebe429ea02633151aad"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:b42f4d86b451c2f9d06ffb4f8bbc776e04df3ba070b9fe2657804b1b40277c48"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cdaefac66e8b8f30e37a9b4768a391e1f8a16a7526d5bc77a7928408ef68e93"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:b738f7e648735714bbb82bdfd030203360cfeab7f6e8a34772b3c8c8b820568c"}, - {file = "lxml-6.0.2-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daf42de090d59db025af61ce6bdb2521f0f102ea0e6ea310f13c17610a97da4c"}, - {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:66328dabea70b5ba7e53d94aa774b733cf66686535f3bc9250a7aab53a91caaf"}, - {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:e237b807d68a61fc3b1e845407e27e5eb8ef69bc93fe8505337c1acb4ee300b6"}, - {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:ac02dc29fd397608f8eb15ac1610ae2f2f0154b03f631e6d724d9e2ad4ee2c84"}, - {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:817ef43a0c0b4a77bd166dc9a09a555394105ff3374777ad41f453526e37f9cb"}, - {file = "lxml-6.0.2-cp39-cp39-win32.whl", hash = "sha256:bc532422ff26b304cfb62b328826bd995c96154ffd2bac4544f37dbb95ecaa8f"}, - {file = "lxml-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:995e783eb0374c120f528f807443ad5a83a656a8624c467ea73781fc5f8a8304"}, - {file = "lxml-6.0.2-cp39-cp39-win_arm64.whl", hash = "sha256:08b9d5e803c2e4725ae9e8559ee880e5328ed61aa0935244e0515d7d9dbec0aa"}, - {file = "lxml-6.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e748d4cf8fef2526bb2a589a417eba0c8674e29ffcb570ce2ceca44f1e567bf6"}, - {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4ddb1049fa0579d0cbd00503ad8c58b9ab34d1254c77bc6a5576d96ec7853dba"}, - {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cb233f9c95f83707dae461b12b720c1af9c28c2d19208e1be03387222151daf5"}, - {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc456d04db0515ce3320d714a1eac7a97774ff0849e7718b492d957da4631dd4"}, - {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2613e67de13d619fd283d58bda40bff0ee07739f624ffee8b13b631abf33083d"}, - {file = "lxml-6.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:24a8e756c982c001ca8d59e87c80c4d9dcd4d9b44a4cbeb8d9be4482c514d41d"}, - {file = "lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700"}, - {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee"}, - {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f"}, - {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9"}, - {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a"}, - {file = "lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e"}, - {file = "lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62"}, + {file = "lxml-6.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60"}, + {file = "lxml-6.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067"}, + {file = "lxml-6.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a"}, + {file = "lxml-6.1.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa"}, + {file = "lxml-6.1.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383"}, + {file = "lxml-6.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1"}, + {file = "lxml-6.1.1-cp310-cp310-win32.whl", hash = "sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a"}, + {file = "lxml-6.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5"}, + {file = "lxml-6.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485"}, + {file = "lxml-6.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2"}, + {file = "lxml-6.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8"}, + {file = "lxml-6.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83"}, + {file = "lxml-6.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6"}, + {file = "lxml-6.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c"}, + {file = "lxml-6.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08"}, + {file = "lxml-6.1.1-cp311-cp311-win32.whl", hash = "sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621"}, + {file = "lxml-6.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28"}, + {file = "lxml-6.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b"}, + {file = "lxml-6.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7"}, + {file = "lxml-6.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a"}, + {file = "lxml-6.1.1-cp312-cp312-win32.whl", hash = "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a"}, + {file = "lxml-6.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77"}, + {file = "lxml-6.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f"}, + {file = "lxml-6.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736"}, + {file = "lxml-6.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947"}, + {file = "lxml-6.1.1-cp313-cp313-win32.whl", hash = "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca"}, + {file = "lxml-6.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660"}, + {file = "lxml-6.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc"}, + {file = "lxml-6.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0"}, + {file = "lxml-6.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb"}, + {file = "lxml-6.1.1-cp314-cp314-win32.whl", hash = "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603"}, + {file = "lxml-6.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137"}, + {file = "lxml-6.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf"}, + {file = "lxml-6.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee"}, + {file = "lxml-6.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e"}, + {file = "lxml-6.1.1-cp314-cp314t-win32.whl", hash = "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1"}, + {file = "lxml-6.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e"}, + {file = "lxml-6.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c"}, + {file = "lxml-6.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6689e828a94eee4f139408c337bb198e014724bb8a8c26d3cfac49d119ed69a6"}, + {file = "lxml-6.1.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bdebcc8a75d38c7598dfb2c9ed852d7a9eb4a10d6e2d0764b919b802bf32ac88"}, + {file = "lxml-6.1.1-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8be8ad51249698103d24b0571df35a10990fbe93dd043b6c024172189485f5e3"}, + {file = "lxml-6.1.1-cp38-cp38-manylinux_2_28_i686.whl", hash = "sha256:76447f65250ed2501ead1a1552f5ce8edff159a86f308348e6a9c4acb5e1f1b4"}, + {file = "lxml-6.1.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ffecec8eb889b58ba9be5b95fb1cc78e22ea8eedea38e8736a1568fe1979250e"}, + {file = "lxml-6.1.1-cp38-cp38-win32.whl", hash = "sha256:c674693f055fa2495de12292cb45e9944199d8eaef5a2dec45175c7c61cb73e3"}, + {file = "lxml-6.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:55b03549819867ea141c0202242c4816c82e52ec36e7e648db9d8da5a3dc3ed6"}, + {file = "lxml-6.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9f79d5325907f13e1be0b3e4dacc1049d1dffc4aeee3c995284bea5fe0fab7d"}, + {file = "lxml-6.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:83b6b30eb131da7a75b601f28c5d6971e6ed3e887919bf6b6a1ad3c2df289080"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:441dd227fa0690eb9fc81edabc63cdcefc212bba99b906dcf6e32cc1a9d3e533"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e07c65f443c887bbcf31cc1771d932ecc192a5273943589b3c7572b749f1ffb2"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5bec7d03d78d853597d6107854c2310ce3f761fd218fe9fe91d5101fcf6c2efe"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f76acfb5f68ba982635a53fd985a8044be98a35b43232c2a1ee235ffab3e1dd"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_28_i686.whl", hash = "sha256:8d43ca737b20e106e4aebc42b2f3ae19f00ba63d7eb731698ee083d72d15646f"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:32ab449a5486f6c758e849bb86710d0e45edc24a04e250c01555f8f5653958f8"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53c909b62a0532183542fed00c5a7218258c56292d409bc789886fe1cb04c438"}, + {file = "lxml-6.1.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:640f97d43d867bcb9c75b3af013b64850756b746cb6bce8ace83b70da3abba9d"}, + {file = "lxml-6.1.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:469e3618338bd7ab5beb412d2439825479fcf0dab99e394ca563dbc4eaf6c834"}, + {file = "lxml-6.1.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:aae97dfdb60715c164419ac2532a76d013c3918a665eb6cb7288098b5f349aaf"}, + {file = "lxml-6.1.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c9a4b821dc7055bf9e05ff5719e18ec501f75c0f0bbfabd573b277559780833d"}, + {file = "lxml-6.1.1-cp39-cp39-win32.whl", hash = "sha256:639f6c857d91d9be29bd7502348d6736dab168b54b5158cd899abf11684dc186"}, + {file = "lxml-6.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:34c2d737beabfe35baada43941ed519251e9a12e779031496bcd5d539fcfd730"}, + {file = "lxml-6.1.1-cp39-cp39-win_arm64.whl", hash = "sha256:07a4a68e286ee7a1ed7dfb8af83e615757c0ccfe9f18c6b4ea6771388d9ba8c9"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84"}, + {file = "lxml-6.1.1.tar.gz", hash = "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40"}, ] [package.extras] @@ -1968,34 +2069,15 @@ html-clean = ["lxml_html_clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -[[package]] -name = "mako" -version = "1.3.10" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59"}, - {file = "mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28"}, -] - -[package.dependencies] -MarkupSafe = ">=0.9.2" - -[package.extras] -babel = ["Babel"] -lingua = ["lingua"] -testing = ["pytest"] - [[package]] name = "markdown-it-py" -version = "4.0.0" +version = "4.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.10" files = [ - {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, - {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, + {file = "markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a"}, + {file = "markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49"}, ] [package.dependencies] @@ -2008,17 +2090,17 @@ linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins (>=0.5.0)"] profiling = ["gprof2dot"] rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "pytest-timeout", "requests"] [[package]] name = "markdownify" -version = "1.2.2" +version = "1.2.3" description = "Convert HTML to markdown." optional = false python-versions = "*" files = [ - {file = "markdownify-1.2.2-py3-none-any.whl", hash = "sha256:3f02d3cc52714084d6e589f70397b6fc9f2f3a8531481bf35e8cc39f975e186a"}, - {file = "markdownify-1.2.2.tar.gz", hash = "sha256:b274f1b5943180b031b699b199cbaeb1e2ac938b75851849a31fd0c3d6603d09"}, + {file = "markdownify-1.2.3-py3-none-any.whl", hash = "sha256:a189a0bedfd14009030fde5f85bb6f77c56897cb839b5c25315dd7d4e3e290ba"}, + {file = "markdownify-1.2.3.tar.gz", hash = "sha256:1a176f05522c8a2cb1dd3ab9d307dcdadbed5c26ae717855bfc42b3b6d38d937"}, ] [package.dependencies] @@ -2125,27 +2207,27 @@ files = [ [[package]] name = "mcp" -version = "1.26.0" +version = "1.29.0" description = "Model Context Protocol SDK" optional = false python-versions = ">=3.10" files = [ - {file = "mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca"}, - {file = "mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66"}, + {file = "mcp-1.29.0-py3-none-any.whl", hash = "sha256:f5a075bb611f23d6f4d080c6a1699fa62772eebc562ba9e66b306ddde1c755f7"}, + {file = "mcp-1.29.0.tar.gz", hash = "sha256:52d01f334de1868cc3bb2d6604931126a67631f99a6c5d3b82ba47290315ec36"}, ] [package.dependencies] anyio = ">=4.5" -httpx = ">=0.27.1" +httpx = ">=0.27.1,<1.0.0" httpx-sse = ">=0.4" jsonschema = ">=4.20.0" -pydantic = ">=2.11.0,<3.0.0" +pydantic = {version = ">=2.11.0,<3.0.0", markers = "python_version < \"3.14\""} pydantic-settings = ">=2.5.2" pyjwt = {version = ">=2.10.1", extras = ["crypto"]} python-multipart = ">=0.0.9" -pywin32 = {version = ">=310", markers = "sys_platform == \"win32\""} +pywin32 = {version = ">=310", markers = "sys_platform == \"win32\" and python_version < \"3.14\""} sse-starlette = ">=1.6.1" -starlette = ">=0.27" +starlette = {version = ">=0.27", markers = "python_version < \"3.14\""} typing-extensions = ">=4.9.0" typing-inspection = ">=0.4.1" uvicorn = {version = ">=0.31.1", markers = "sys_platform != \"emscripten\""} @@ -2323,56 +2405,71 @@ files = [ [[package]] name = "mypy" -version = "1.19.1" +version = "2.3.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, - {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, - {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, - {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, - {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, - {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, - {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, - {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, - {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, - {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, - {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, - {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, - {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, - {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, - {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, - {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, - {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, - {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, - {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, - {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, - {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, - {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, - {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, - {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, - {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, - {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, - {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, - {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, - {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, - {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, - {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, - {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, - {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, - {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, - {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, - {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, - {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, - {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, + {file = "mypy-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:57a936373fc690c43a8cd7e7e12a35148e4ec5aa7698ad7fc0a9f918bdc5be41"}, + {file = "mypy-2.3.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d00d769056bde2f4e69c175071eba45cfb44fa1ed92bdfbfe64a93e0543b0cf0"}, + {file = "mypy-2.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2166b29228835e1f88ff411e96639e6ca3c7fdde84b62ec211f70f86b4051167"}, + {file = "mypy-2.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:83d36c2924df7426333abe7faf4724a7e1aab0d9fd41625e81b4683034b80c13"}, + {file = "mypy-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:f12fdb70459d0060dea40b29e52163a961b156106d68d57882a6a9f648983a53"}, + {file = "mypy-2.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:e099200a1b1b1223a4951f0a90cbff1b8c91b250ba599dab1f7217a628144d90"}, + {file = "mypy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94f04929f1c44c35fb0061e912087edaf504acede963a4a7d00680bd089d8531"}, + {file = "mypy-2.3.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5d716048611e85ca9eefb2e1baa5d73ede389b5820ded260ea27c757d667af8"}, + {file = "mypy-2.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b091a455111214cb5c9d54a57b9618e9a49f9fe2a42e4e1ac86e9d104ed96ce8"}, + {file = "mypy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df12e20c9efd614738c71b390007ecd0181125afc4ccafca04d78a1d2eed2c01"}, + {file = "mypy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:52eaf3a155f35cf80b40220288c861eb45f14a2340c1f6cbfbdb0feff32879d1"}, + {file = "mypy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9b4eacbee8a69836c06eff6d0dd4e134a07c2b047755b30c08625fe214f322c6"}, + {file = "mypy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a32bbbb940af990d3be0b8af321c7b6815bb1b3b48142fe7459b9cc5f58959ff"}, + {file = "mypy-2.3.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff715e45b2231a8e85de1d163d1b42791e4d7aab8f5145f85fee1b710b735aff"}, + {file = "mypy-2.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:858fc57d3d91fa728e33e7ad71def60fc6272694607b306cd3292db53ae39080"}, + {file = "mypy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:851833db876e7b650f93719c74b7879a08e338979c96054fdfc3bfd90a486355"}, + {file = "mypy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:4c5095a327483591c94e0c8d3ef9e50d4ab1369b541eae007c1f23bc2a41f6bb"}, + {file = "mypy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfe022634a2a195406bd469e888d2eaf193b02ba7e607391cd7640374aaae3b"}, + {file = "mypy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:114dff494000f18bd10d5d95d84b8567b26da60279ecbe838131841df20e635d"}, + {file = "mypy-2.3.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8637731bb5eee3671eb2c3200827aa3564ed8a9309ecee4d1afe77e6d031bdb"}, + {file = "mypy-2.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c80fbc405ed8020f5ff3802dc18cf060197bcdd3fbdd6a26ef2fd34dfdd5226"}, + {file = "mypy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:84081f538ce27375045c02e3d7f81bd11d853400621ae245d87ce7b6c420ec74"}, + {file = "mypy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:e9144ac16fde007096f9563eb2041b4433c2d705c4218edeb79e7e9d01035ee6"}, + {file = "mypy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:77ad9529e67dca28e511f5cd5671436584ce91f6d3bac159a353158187b986ac"}, + {file = "mypy-2.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:192abaedf75da1bc0b1cef104927e70ec49c1ef0031cc4825c7ee10a438ed24d"}, + {file = "mypy-2.3.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf678dffd16efcda2c15cbd30e9ecc0081388e29ea23687a88e686ed92638dc3"}, + {file = "mypy-2.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e036f06b41630f4c8a1d48f9ac6aa26acc65f8be089973f5519da643318f03f"}, + {file = "mypy-2.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71af9c8a894e862b58e92abb08e53b05a384a1e5e5d6dc7cda59126211a53d82"}, + {file = "mypy-2.3.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3c80cd23d85368bdd9f37d5231dfd97d35bcbf5bf41af96ef3a9b078ad1957f9"}, + {file = "mypy-2.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:4956f34d145e145562a0a0bf367f642bbc85c04ec2baf47ae015947c3169a85d"}, + {file = "mypy-2.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:cfb12e360242d23d91f5e978d94f58ea66acf5804c4fb6f2f794a20d4cb1b595"}, + {file = "mypy-2.3.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5f1c50bb05b64e2026b52867e8d21106f01313c744a2c4ecc34c90d12e8d6e2"}, + {file = "mypy-2.3.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:667196b352f4cf304ded4c10f90cfc179263a1acfb3cdcfa984bdfd340d498bc"}, + {file = "mypy-2.3.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9c53e395c12cad2c6d4b67d5da7c6057638a132d85c08b73646b18f802a0045"}, + {file = "mypy-2.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:18162b128c3f9c703cd35f5537446900b0d21a2549aa7a95d21380d2ef643fb0"}, + {file = "mypy-2.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:30c0477d4aab7b7f39c8397dc877f2c96b9fe5588ec379f372c56eb63d599f63"}, + {file = "mypy-2.3.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6941ab3619377bc3f32ca02876b07d27f216f5201604b664d3937ea0fdd23bb4"}, + {file = "mypy-2.3.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6f041a6de52c9217ca125e78ba0a335cb7fd98a1c0580978e49ab2b126f70b57"}, + {file = "mypy-2.3.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5159ae60f5dbc3a498af5ba8365505808ac8031bc63f9e00304ad545d40bdd9b"}, + {file = "mypy-2.3.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47a8a7a0a7f6f6e63995c0ac36fa0c07b127413fdc81f0439b7f3dccafd33561"}, + {file = "mypy-2.3.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2329c0501293d4e1f33bc15d04d6304d65a1cdda967ee93a05c1e681a3923133"}, + {file = "mypy-2.3.1-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:bb26deed807bdb0457cf3e3f1cd7c4a1cf9d66864eaf1b4a61e06805d4c6b1f9"}, + {file = "mypy-2.3.1-cp315-cp315-win_amd64.whl", hash = "sha256:375d7013876a8233b2d05be185bfa09f689696cd999ce8b1cfe6acac5c80e8a3"}, + {file = "mypy-2.3.1-cp315-cp315-win_arm64.whl", hash = "sha256:586b3612214cceabb3c0f588c97e7d1e535393f06a60e912e994f6b3ace97523"}, + {file = "mypy-2.3.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:ef0c6335cda9d807f8193d8ff6204a72bc909fa9882aacbca14f43cdb7188306"}, + {file = "mypy-2.3.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e598c8c66401d26b150872154a286e6d484cf2789c3bb28a7556806298423021"}, + {file = "mypy-2.3.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eda22fd4efa9dcd39331d1dede9b5b8b8a7fd69af07592e778433da98610d29e"}, + {file = "mypy-2.3.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:2a0ba2e57847849fb0d1fcdabb32786d223095ed8bc121dfe322bcdb3d9c46bc"}, + {file = "mypy-2.3.1-cp315-cp315t-win_amd64.whl", hash = "sha256:3f7e865dd51f235f60a2dbcd8728a1c095f5ca28f095d48a725b84cd935735c4"}, + {file = "mypy-2.3.1-cp315-cp315t-win_arm64.whl", hash = "sha256:8ad80807dc3ab8ea978b1b2b6e4a657194ace1d4ef03e0e731aff1abd517da29"}, + {file = "mypy-2.3.1-py3-none-any.whl", hash = "sha256:6ed5c7e3419083268e5c9258bd1c1ef91af44a9e89374dbcaf37b775716e72eb"}, + {file = "mypy-2.3.1.tar.gz", hash = "sha256:47c1b1207258513a9d93495f69c8be9de73916186f0e52703e8c461b7a623419"}, ] [package.dependencies] -librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} +ast-serialize = ">=0.6.0,<1.0.0" +librt = {version = ">=0.13.0", markers = "platform_python_implementation != \"PyPy\""} mypy_extensions = ">=1.0.0" -pathspec = ">=0.9.0" -typing_extensions = ">=4.6.0" +pathspec = ">=1.0.0" +typing_extensions = {version = ">=4.6.0", markers = "python_version < \"3.15\""} [package.extras] dmypy = ["psutil (>=4.0)"] @@ -2392,96 +2489,15 @@ files = [ {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] -[[package]] -name = "numpy" -version = "2.4.2" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.11" -files = [ - {file = "numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825"}, - {file = "numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1"}, - {file = "numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7"}, - {file = "numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73"}, - {file = "numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1"}, - {file = "numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32"}, - {file = "numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390"}, - {file = "numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413"}, - {file = "numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda"}, - {file = "numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695"}, - {file = "numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3"}, - {file = "numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a"}, - {file = "numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1"}, - {file = "numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e"}, - {file = "numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27"}, - {file = "numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548"}, - {file = "numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f"}, - {file = "numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460"}, - {file = "numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba"}, - {file = "numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f"}, - {file = "numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85"}, - {file = "numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa"}, - {file = "numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c"}, - {file = "numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979"}, - {file = "numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98"}, - {file = "numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef"}, - {file = "numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7"}, - {file = "numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499"}, - {file = "numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb"}, - {file = "numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7"}, - {file = "numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110"}, - {file = "numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622"}, - {file = "numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71"}, - {file = "numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262"}, - {file = "numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913"}, - {file = "numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab"}, - {file = "numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82"}, - {file = "numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f"}, - {file = "numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554"}, - {file = "numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257"}, - {file = "numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657"}, - {file = "numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b"}, - {file = "numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1"}, - {file = "numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b"}, - {file = "numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000"}, - {file = "numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1"}, - {file = "numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74"}, - {file = "numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a"}, - {file = "numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325"}, - {file = "numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909"}, - {file = "numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a"}, - {file = "numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a"}, - {file = "numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75"}, - {file = "numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05"}, - {file = "numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308"}, - {file = "numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef"}, - {file = "numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d"}, - {file = "numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8"}, - {file = "numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5"}, - {file = "numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e"}, - {file = "numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a"}, - {file = "numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443"}, - {file = "numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236"}, - {file = "numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0"}, - {file = "numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae"}, -] - [[package]] name = "openai" -version = "2.16.0" +version = "2.54.0" description = "The official Python library for the openai API" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "openai-2.16.0-py3-none-any.whl", hash = "sha256:5f46643a8f42899a84e80c38838135d7038e7718333ce61396994f887b09a59b"}, - {file = "openai-2.16.0.tar.gz", hash = "sha256:42eaa22ca0d8ded4367a77374104d7a2feafee5bd60a107c3c11b5243a11cd12"}, + {file = "openai-2.54.0-py3-none-any.whl", hash = "sha256:89089789197ccdb87f173a03145ed1598d00795220c93e96cf712b1cbf5e5f2b"}, + {file = "openai-2.54.0.tar.gz", hash = "sha256:e3e6f8bc1ba30ddf381ace1a14340eed381cb984a1a59bd0f34b5be3b5d49cfa"}, ] [package.dependencies] @@ -2492,150 +2508,116 @@ jiter = ">=0.10.0,<1" pydantic = ">=1.9.0,<3" sniffio = "*" tqdm = ">4" -typing-extensions = ">=4.11,<5" +typing-extensions = ">=4.14,<5" [package.extras] -aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.9)"] +aiohttp = ["aiohttp (>=3.14.1)", "httpx-aiohttp (>=0.1.9)"] +bedrock = ["botocore (>=1.40.0,<2)"] datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] +httpx2 = ["anyio (>=4.10.0,<5)", "httpx (>=0.25.1,<1)", "httpx2 (>=2.7.0,<3)"] realtime = ["websockets (>=13,<16)"] voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] -[[package]] -name = "optuna" -version = "4.7.0" -description = "A hyperparameter optimization framework" -optional = false -python-versions = ">=3.9" -files = [ - {file = "optuna-4.7.0-py3-none-any.whl", hash = "sha256:e41ec84018cecc10eabf28143573b1f0bde0ba56dba8151631a590ecbebc1186"}, - {file = "optuna-4.7.0.tar.gz", hash = "sha256:d91817e2079825557bd2e97de2e8c9ae260bfc99b32712502aef8a5095b2d2c0"}, -] - -[package.dependencies] -alembic = ">=1.5.0" -colorlog = "*" -numpy = "*" -packaging = ">=20.0" -PyYAML = "*" -sqlalchemy = ">=1.4.2" -tqdm = "*" - -[package.extras] -checking = ["mypy", "mypy_boto3_s3", "ruff", "scipy-stubs", "types-PyYAML", "types-redis", "types-setuptools", "types-tqdm", "typing_extensions (>=3.10.0.0)"] -document = ["ase", "cmaes (>=0.12.0)", "fvcore", "kaleido (<0.4)", "lightgbm", "matplotlib (!=3.6.0)", "pandas", "pillow", "plotly (>=4.9.0)", "scikit-learn", "sphinx", "sphinx-copybutton", "sphinx-gallery", "sphinx-notfound-page", "sphinx_rtd_theme (>=1.2.0)", "torch", "torchvision"] -optional = ["boto3", "cmaes (>=0.12.0)", "google-cloud-storage", "greenlet", "grpcio", "matplotlib (!=3.6.0)", "pandas", "plotly (>=4.9.0)", "protobuf (>=5.28.1)", "redis", "scikit-learn (>=0.24.2)", "scipy", "torch"] -test = ["fakeredis[lua]", "greenlet", "grpcio", "kaleido (<0.4)", "moto", "protobuf (>=5.28.1)", "pytest", "pytest-xdist", "scipy (>=1.9.2)", "torch"] - [[package]] name = "orjson" -version = "3.11.7" +version = "3.12.0" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.10" files = [ - {file = "orjson-3.11.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a02c833f38f36546ba65a452127633afce4cf0dd7296b753d3bb54e55e5c0174"}, - {file = "orjson-3.11.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63c6e6738d7c3470ad01601e23376aa511e50e1f3931395b9f9c722406d1a67"}, - {file = "orjson-3.11.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043d3006b7d32c7e233b8cfb1f01c651013ea079e08dcef7189a29abd8befe11"}, - {file = "orjson-3.11.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57036b27ac8a25d81112eb0cc9835cd4833c5b16e1467816adc0015f59e870dc"}, - {file = "orjson-3.11.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:733ae23ada68b804b222c44affed76b39e30806d38660bf1eb200520d259cc16"}, - {file = "orjson-3.11.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5fdfad2093bdd08245f2e204d977facd5f871c88c4a71230d5bcbd0e43bf6222"}, - {file = "orjson-3.11.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cededd6738e1c153530793998e31c05086582b08315db48ab66649768f326baa"}, - {file = "orjson-3.11.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:14f440c7268c8f8633d1b3d443a434bd70cb15686117ea6beff8fdc8f5917a1e"}, - {file = "orjson-3.11.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3a2479753bbb95b0ebcf7969f562cdb9668e6d12416a35b0dda79febf89cdea2"}, - {file = "orjson-3.11.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:71924496986275a737f38e3f22b4e0878882b3f7a310d2ff4dc96e812789120c"}, - {file = "orjson-3.11.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4a9eefdc70bf8bf9857f0290f973dec534ac84c35cd6a7f4083be43e7170a8f"}, - {file = "orjson-3.11.7-cp310-cp310-win32.whl", hash = "sha256:ae9e0b37a834cef7ce8f99de6498f8fad4a2c0bf6bfc3d02abd8ed56aa15b2de"}, - {file = "orjson-3.11.7-cp310-cp310-win_amd64.whl", hash = "sha256:d772afdb22555f0c58cfc741bdae44180122b3616faa1ecadb595cd526e4c993"}, - {file = "orjson-3.11.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9487abc2c2086e7c8eb9a211d2ce8855bae0e92586279d0d27b341d5ad76c85c"}, - {file = "orjson-3.11.7-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:79cacb0b52f6004caf92405a7e1f11e6e2de8bdf9019e4f76b44ba045125cd6b"}, - {file = "orjson-3.11.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2e85fe4698b6a56d5e2ebf7ae87544d668eb6bde1ad1226c13f44663f20ec9e"}, - {file = "orjson-3.11.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b8d14b71c0b12963fe8a62aac87119f1afdf4cb88a400f61ca5ae581449efcb5"}, - {file = "orjson-3.11.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91c81ef070c8f3220054115e1ef468b1c9ce8497b4e526cb9f68ab4dc0a7ac62"}, - {file = "orjson-3.11.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:411ebaf34d735e25e358a6d9e7978954a9c9d58cfb47bc6683cdc3964cd2f910"}, - {file = "orjson-3.11.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a16bcd08ab0bcdfc7e8801d9c4a9cc17e58418e4d48ddc6ded4e9e4b1a94062b"}, - {file = "orjson-3.11.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c0b51672e466fd7e56230ffbae7f1639e18d0ce023351fb75da21b71bc2c960"}, - {file = "orjson-3.11.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:136dcd6a2e796dfd9ffca9fc027d778567b0b7c9968d092842d3c323cef88aa8"}, - {file = "orjson-3.11.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7ba61079379b0ae29e117db13bda5f28d939766e410d321ec1624afc6a0b0504"}, - {file = "orjson-3.11.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0527a4510c300e3b406591b0ba69b5dc50031895b0a93743526a3fc45f59d26e"}, - {file = "orjson-3.11.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a709e881723c9b18acddcfb8ba357322491ad553e277cf467e1e7e20e2d90561"}, - {file = "orjson-3.11.7-cp311-cp311-win32.whl", hash = "sha256:c43b8b5bab288b6b90dac410cca7e986a4fa747a2e8f94615aea407da706980d"}, - {file = "orjson-3.11.7-cp311-cp311-win_amd64.whl", hash = "sha256:6543001328aa857187f905308a028935864aefe9968af3848401b6fe80dbb471"}, - {file = "orjson-3.11.7-cp311-cp311-win_arm64.whl", hash = "sha256:1ee5cc7160a821dfe14f130bc8e63e7611051f964b463d9e2a3a573204446a4d"}, - {file = "orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f"}, - {file = "orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b"}, - {file = "orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a"}, - {file = "orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10"}, - {file = "orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa"}, - {file = "orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8"}, - {file = "orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f"}, - {file = "orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad"}, - {file = "orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867"}, - {file = "orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d"}, - {file = "orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab"}, - {file = "orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2"}, - {file = "orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f"}, - {file = "orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74"}, - {file = "orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5"}, - {file = "orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733"}, - {file = "orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4"}, - {file = "orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785"}, - {file = "orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539"}, - {file = "orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1"}, - {file = "orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1"}, - {file = "orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705"}, - {file = "orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace"}, - {file = "orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b"}, - {file = "orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157"}, - {file = "orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3"}, - {file = "orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223"}, - {file = "orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3"}, - {file = "orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757"}, - {file = "orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539"}, - {file = "orjson-3.11.7-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:de0a37f21d0d364954ad5de1970491d7fbd0fb1ef7417d4d56a36dc01ba0c0a0"}, - {file = "orjson-3.11.7-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c2428d358d85e8da9d37cba18b8c4047c55222007a84f97156a5b22028dfbfc0"}, - {file = "orjson-3.11.7-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4bc6c6ac52cdaa267552544c73e486fecbd710b7ac09bc024d5a78555a22f6"}, - {file = "orjson-3.11.7-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd0d68edd7dfca1b2eca9361a44ac9f24b078de3481003159929a0573f21a6bf"}, - {file = "orjson-3.11.7-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:623ad1b9548ef63886319c16fa317848e465a21513b31a6ad7b57443c3e0dcf5"}, - {file = "orjson-3.11.7-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e776b998ac37c0396093d10290e60283f59cfe0fc3fccbd0ccc4bd04dd19892"}, - {file = "orjson-3.11.7-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c6c3af76716f4a9c290371ba2e390ede06f6603edb277b481daf37f6f464e"}, - {file = "orjson-3.11.7-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56df3239294ea5964adf074c54bcc4f0ccd21636049a2cf3ca9cf03b5d03cf1"}, - {file = "orjson-3.11.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda117c4148e81f746655d5a3239ae9bd00cb7bc3ca178b5fc5a5997e9744183"}, - {file = "orjson-3.11.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:23d6c20517a97a9daf1d48b580fcdc6f0516c6f4b5038823426033690b4d2650"}, - {file = "orjson-3.11.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8ff206156006da5b847c9304b6308a01e8cdbc8cce824e2779a5ba71c3def141"}, - {file = "orjson-3.11.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:962d046ee1765f74a1da723f4b33e3b228fe3a48bd307acce5021dfefe0e29b2"}, - {file = "orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576"}, - {file = "orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1"}, - {file = "orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d"}, - {file = "orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49"}, + {file = "orjson-3.12.0-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:747843254519dd43b93eee3153a19e5a509334320c4d2f823ec879232db5c796"}, + {file = "orjson-3.12.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:7c2ad193c8004254f34b499f3bd2c80f043d10754aff2b38f93da574f4883f98"}, + {file = "orjson-3.12.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:bc7a872f03522d90e0429e6c0c5cd23084f767bedcb4c58048eec19294613344"}, + {file = "orjson-3.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18a87929f31d94a77f7dc93cf527e91f39ce7fe7813d588a4de2507efd32a387"}, + {file = "orjson-3.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9683ee9ea0659da64f36574ef675b8a86330c34c19ea75db1fb93c3ff99e0ef"}, + {file = "orjson-3.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:103b5db66aa53c1f9e88c2524be4f383e831ba7dfd5f9f5af6336a177c622f11"}, + {file = "orjson-3.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd57d79aefa3f84eec851d6de7a366795b9345cfaf17f82b4820430a7a5fa241"}, + {file = "orjson-3.12.0-cp310-cp310-win32.whl", hash = "sha256:3dbce9b6b3074b31a5d5dd322a9c4e5b16f206091ece4194c2e36952847a105e"}, + {file = "orjson-3.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:3bb17a06f9bd15237b3216c044209fe92597379124018cfc196fbb846cde64df"}, + {file = "orjson-3.12.0-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a94f0f0c6fcbb2b5bd9734c57a489c7584a732bbdf04a39e8c83b861e9d03e92"}, + {file = "orjson-3.12.0-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:a696529ec96a90d9a5f9570207efe403c8b08f8e4aa2783ee3403511e2fdfa10"}, + {file = "orjson-3.12.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:e4ac5059baab4b3acbd99485de019ff8cda0fdf34b61fa74f7197a53db78bfe8"}, + {file = "orjson-3.12.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8e29957429c35bbb5a185a119c523aa2428b7bbf1a293724c7b9375ed8f892a3"}, + {file = "orjson-3.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dce0166feb0a737ab84f598c9a338cbc0b764a036617aa686194f53c7eba0c3e"}, + {file = "orjson-3.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9caf3d09f47c3c70c4451ada20ef9bc4a4cdffa26f49862cf0a253b329aae2d5"}, + {file = "orjson-3.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9dca132b1fda5565088e65a6b6e742285e0aeceb6fae549fa8863e16c7d3998"}, + {file = "orjson-3.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a791f793b287bbc135b8e87c34e35c8bfc693e2a8a620fab1ae682b925f9a32e"}, + {file = "orjson-3.12.0-cp311-cp311-win32.whl", hash = "sha256:31ed278a36304390adc3eec5d7f6fd593a7c3e99e5a06cd07866396c4b1b4710"}, + {file = "orjson-3.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb2539159dfe8d371914f354360fa50e4a577cc89222a3828b9650a5e5040252"}, + {file = "orjson-3.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:61318b6de893c7a9d9f3e5ecbadccbfc26a7eb417ccc7bbf0771de3b4d72f868"}, + {file = "orjson-3.12.0-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:aa3e43a6846e91d7bde3d5a9c66090fcd8744f569a9b6cffc5e1ca38f6a461c0"}, + {file = "orjson-3.12.0-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:11edb4660a6680abee9788a3a9072208a2c96538cc1322bd79542065229d8e54"}, + {file = "orjson-3.12.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:2d3a9da945a4d96ae758fdaaca56742e6b73b6fd554c5d8876f252a6dad70b83"}, + {file = "orjson-3.12.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:92ffc09e07233a6ab6d4e067f7841edcbcc134cb4812155cf171ea5255a421d7"}, + {file = "orjson-3.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf44e374aadde77b1f6109f1030be51433eb61984379852766b6f4e187db7b1e"}, + {file = "orjson-3.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1192a7021b6d071aaf909864f6e924d6a2675ca360485b972b8401749311750b"}, + {file = "orjson-3.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:53c0c474a9d9aff9aebfc0c88de1f28f843d940e6e3a80729abdf6a20274356f"}, + {file = "orjson-3.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:532ff8cd4bd59a327a953a7dcde922c7fc25b85e29721bb8633265430d3a3873"}, + {file = "orjson-3.12.0-cp312-cp312-win32.whl", hash = "sha256:a6cf4b18e7de173f209f2084ffbd736dd72389a396326ee80a7022168be232e5"}, + {file = "orjson-3.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:010811c1b69773450a01cef97727a67b223242f350b77d4ca000e59a9ef2155a"}, + {file = "orjson-3.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:ad29eece0c601737f2a60edc2752a84e7a0785df3efb62e3012834700a5afe0d"}, + {file = "orjson-3.12.0-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9a36ec60f1796f9a3f13e3b98390295e17a1c7c10155b448d264098bf9ee5900"}, + {file = "orjson-3.12.0-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:ad0422b92d5195443a39f80c3bcf731cc2e00f153bd32063a47b73b057bd0f03"}, + {file = "orjson-3.12.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:5a0fdbc216388f653d3752ff310e710f59253bd4ed6a2bfb3f4f06b84714bbd8"}, + {file = "orjson-3.12.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2eb5c56e534127b2b8fa38d2363c8b1b8190367ee0d1d16c041517d880843b94"}, + {file = "orjson-3.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:784106539f4b9d4b930e0b4eb8d45168507dae001945e71b4675a367f1e5e806"}, + {file = "orjson-3.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c680706fc8396d95e7c4c1f9482563f552137aef91b57237a3ad5aaf64629df"}, + {file = "orjson-3.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:83445adc40cba26d6d621185a45128ce455b766af368cad2ab64b970603a7978"}, + {file = "orjson-3.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:644d005bc82f917337a95ce270c9f6f92f9834c2bed7b1477572f8db00784222"}, + {file = "orjson-3.12.0-cp313-cp313-win32.whl", hash = "sha256:d8e78d3d93705e3d27cc17cdb209e44d7a8ea203010cac6ce9c7ffc1ae1996f1"}, + {file = "orjson-3.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b85931be5b6763c31283805c9bdaae1ca03ad9f6f12a15f1cbf6745b907932c2"}, + {file = "orjson-3.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:6a31348d7dfa64cd9c78bd1f510ff44c48fe64d71094e6b90e364dba3b55949e"}, + {file = "orjson-3.12.0-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9e6fee342a48760e854d743e7a81534d8e2925a6f46e09f750cf56b50fd1de5d"}, + {file = "orjson-3.12.0-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:8c3bb86dd10f39b3fbf434b7d5dc7cac77d6fc8ac572ae30a10731ede2c4b647"}, + {file = "orjson-3.12.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:2bb3ce43203936072dd8b4917b01d3aecfc02329bfb42510cb7cfb24708adc9c"}, + {file = "orjson-3.12.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:6a2a79c89984dc719817d388c8709e0efc2a2795a934eaa746b4882eb6045adc"}, + {file = "orjson-3.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f06dd838d1e07d9b1de0932ec0485ec92c4d5f5d1ad4817a656268c3e88be1e1"}, + {file = "orjson-3.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6b11be792c3d2c6a4be2af4ebf97a68d0bf5f580aca6e86a418a354f6cc846a"}, + {file = "orjson-3.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:477ecaf6b9f88f873341b91fcc736119ca81b5e002a9f7f308ff5b4f2ce2a70e"}, + {file = "orjson-3.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f3c0683136acdc29afdf88a5bc2f7d3d0e34087788d1d63c0144b805a87a196f"}, + {file = "orjson-3.12.0-cp314-cp314-win32.whl", hash = "sha256:d39f3f5c3927e2dc0913fe5bbc1a2f6b1b9d1bba1de6358340d0ad0d0c00ca92"}, + {file = "orjson-3.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:0b1ac5bf6609b2716c7954011c5fef6254922df029f45d032ee4ebf5d363cbed"}, + {file = "orjson-3.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:50fae885cb073eac7556353ff3df93312b0d5137b0a5056b2bb63f97ed9a93c7"}, + {file = "orjson-3.12.0-cp315-cp315-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:01efac2074fffb4cb1ea3fab7861e9d0f2a26913854a972f5ac760525dbdaf6e"}, + {file = "orjson-3.12.0-cp315-cp315-macosx_15_0_arm64.whl", hash = "sha256:ed4ca42bd55955aa34deedcfdfd0e0c31abf51143aae158ae2bc3520b626e517"}, + {file = "orjson-3.12.0-cp315-cp315-manylinux_2_39_aarch64.whl", hash = "sha256:40f92192227505acca4e2533ce565f8e6b9535f7d0d09b0968452f18b7376b38"}, + {file = "orjson-3.12.0-cp315-cp315-manylinux_2_39_armv7l.whl", hash = "sha256:33efefcf5d88eaf400b47e2eba02f91f319bb9951be61ca500b7d536d3f2079d"}, + {file = "orjson-3.12.0-cp315-cp315-manylinux_2_39_i686.whl", hash = "sha256:8e386b0bc0ddd7cd2056f884b5a0af33592bd01ac66a7ca4b42a65a7e7774a13"}, + {file = "orjson-3.12.0-cp315-cp315-manylinux_2_39_x86_64.whl", hash = "sha256:58c58e1de0006ffb580368d6793c36c7b0b021db066479cf281bf5061e732328"}, + {file = "orjson-3.12.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:08231552159be266a7269555bd9f7c016aee7d9ad6dab06eb58796c5ccb7101c"}, + {file = "orjson-3.12.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:a15f9a891bce5f5cc5d210e3ad8614d4d1b489a56448c099d6d2a7168b2d954a"}, + {file = "orjson-3.12.0-cp315-cp315-win32.whl", hash = "sha256:03091c8a64db4be38746597ceea68f33c238e27acd9bfe99fb59420224ae7a55"}, + {file = "orjson-3.12.0-cp315-cp315-win_amd64.whl", hash = "sha256:2b7bcefb9f40fa242fa6b06377232c048e655747790829609168c01162f60578"}, + {file = "orjson-3.12.0-cp315-cp315-win_arm64.whl", hash = "sha256:859fc4196855890150bb08e649b30d2c93b249b3e3edd0d3bb2231abf8aa8adc"}, + {file = "orjson-3.12.0.tar.gz", hash = "sha256:d14203fb1aae2ad9b3d52f8a0e82aeb10197ef1c9bc61da7f358bd70b00123d5"}, ] [[package]] name = "packaging" -version = "26.0" +version = "26.3" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, - {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, + {file = "packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c"}, + {file = "packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79"}, ] [[package]] name = "pathspec" -version = "1.0.4" +version = "1.1.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.9" files = [ - {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, - {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, + {file = "pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189"}, + {file = "pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"}, ] [package.extras] hyperscan = ["hyperscan (>=0.7)"] optional = ["typing-extensions (>=4)"] re2 = ["google-re2 (>=1.1)"] -tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] [[package]] name = "pluggy" @@ -2654,20 +2636,42 @@ testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "primp" -version = "0.15.0" -description = "HTTP client that can impersonate web browsers, mimicking their headers and `TLS/JA3/JA4/HTTP2` fingerprints" +version = "1.3.1" +description = "HTTP client that can impersonate web browsers" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" files = [ - {file = "primp-0.15.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1b281f4ca41a0c6612d4c6e68b96e28acfe786d226a427cd944baa8d7acd644f"}, - {file = "primp-0.15.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:489cbab55cd793ceb8f90bb7423c6ea64ebb53208ffcf7a044138e3c66d77299"}, - {file = "primp-0.15.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c18b45c23f94016215f62d2334552224236217aaeb716871ce0e4dcfa08eb161"}, - {file = "primp-0.15.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e985a9cba2e3f96a323722e5440aa9eccaac3178e74b884778e926b5249df080"}, - {file = "primp-0.15.0-cp38-abi3-manylinux_2_34_armv7l.whl", hash = "sha256:6b84a6ffa083e34668ff0037221d399c24d939b5629cd38223af860de9e17a83"}, - {file = "primp-0.15.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:592f6079646bdf5abbbfc3b0a28dac8de943f8907a250ce09398cda5eaebd260"}, - {file = "primp-0.15.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5a728e5a05f37db6189eb413d22c78bd143fa59dd6a8a26dacd43332b3971fe8"}, - {file = "primp-0.15.0-cp38-abi3-win_amd64.whl", hash = "sha256:aeb6bd20b06dfc92cfe4436939c18de88a58c640752cf7f30d9e4ae893cdec32"}, - {file = "primp-0.15.0.tar.gz", hash = "sha256:1af8ea4b15f57571ff7fc5e282a82c5eb69bc695e19b8ddeeda324397965b30a"}, + {file = "primp-1.3.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:27b87e6370045a0c65c0e4dfdfacbfe637387d05673ce8ddcce400263f7c27f0"}, + {file = "primp-1.3.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:27a8804eb9a3f641f379ee2b443591428cf85c898816e93d04d3e7b6f229ebcb"}, + {file = "primp-1.3.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:862974796552a51af8e276bb19c5d5e189168ab8bad216aef7ce3726a8d3b1dd"}, + {file = "primp-1.3.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ceb24198994799706f4020a00173ba9c1b491aa9805b1e014d87946677bc3c5d"}, + {file = "primp-1.3.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3298b8afcf0a88ba6622bfc18e78aeb11afbb7d5afa4774f24acf7491f54a2d"}, + {file = "primp-1.3.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b8d38c5a6d0a863274cbcae9678f265fcdcead3c20d12d152244e88f5f2186b"}, + {file = "primp-1.3.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96f831c78ddb5900873f51e294bf9bbb4bbfdac3a2f39ce4023f8c558d299332"}, + {file = "primp-1.3.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329d0c320841f65b39d80801d8bae126732b84ec1094ca17b14fda0bda1b20ff"}, + {file = "primp-1.3.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6c3c67670c38a03e9e8da45b212243d35afc8efa018317c46ecdce47f05329d1"}, + {file = "primp-1.3.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:9409a31028a8c62a609d389554ad4f5339aad075130300cd443beef0336d7179"}, + {file = "primp-1.3.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:88ca36c2bd1b7c64b96ad07ca367d2d111ac8e9670549be5f232da8bf795d21e"}, + {file = "primp-1.3.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:74d13800b501aa003fb05c263d38f8d61656c83a60b2951046c0fc412bc73976"}, + {file = "primp-1.3.1-cp310-abi3-win32.whl", hash = "sha256:09ada1752629fe89d7b128beeb59cb641f404af462e24177ba36aed1cf322299"}, + {file = "primp-1.3.1-cp310-abi3-win_amd64.whl", hash = "sha256:c0d1e294466cd5ec7ef173eedf8df25cbdc050138d40447a906e92b8553e7765"}, + {file = "primp-1.3.1-cp310-abi3-win_arm64.whl", hash = "sha256:43304cb41cbb46f361de49faf1cbdba57f969f628c9297239c7ed8ef0cac420f"}, + {file = "primp-1.3.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:72249a4540d0a8965f36eb9a86cd16801d1c7e8dac2f0b0fa23a0a5a03402d36"}, + {file = "primp-1.3.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:db4e2eaa5707e47899eeba6026f420f9b0108a28c08d63f1826d0cab8d50f06f"}, + {file = "primp-1.3.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d62e7609c98b4bc99c9cecc47f16f332fb8fe1a023002176267b0043dedad0c7"}, + {file = "primp-1.3.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3d692e912c2b25271163ba7719df0afdb733a7e7c3073c9094e9001882463543"}, + {file = "primp-1.3.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c08693517dc160a12c0f9e2565c5319173cef738893a303ff2fb28ecccbd84d"}, + {file = "primp-1.3.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d134ebfa31adc619e4e48289fe3e7eebc8310141560e6a6a04269cc94893d9ab"}, + {file = "primp-1.3.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48e27e7c0e015a6de495cf79c0c8d599ba5f69d091af31572bec2de020522d9c"}, + {file = "primp-1.3.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c3d682df08c1b1f37b1f66b21fd173baebcfcb52490830b12292d8fe89b2147"}, + {file = "primp-1.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fabaac4280df0802377d34b869949d617a0ecf22ca7fd5f9bded3f5c981031f1"}, + {file = "primp-1.3.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f510e5881e0a4c4b9e7dbc03722c316d58454388b88000a0e7bf18a4b36d601e"}, + {file = "primp-1.3.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0504de2901c97903a9c369856a4b186dc90a782d8320652c142b066e697d5a1a"}, + {file = "primp-1.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c3b24e302d95d327e873834b9423823b9c8af2abf5e0bbf57a03f3354cfe528"}, + {file = "primp-1.3.1-cp314-cp314t-win32.whl", hash = "sha256:4346dcef805279028bf4a54bb87dd43d0920130e25b5790689f5c96c9ba0d9e5"}, + {file = "primp-1.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6c55f152a73b6d6af8ac37bdb648d8bbfd7e656f9ef40d87feb3c0d81cee930a"}, + {file = "primp-1.3.1-cp314-cp314t-win_arm64.whl", hash = "sha256:46a529d74583d6ceba52e15bf4c678fcf24e6d669c1ce935262d5490d1b25801"}, + {file = "primp-1.3.1.tar.gz", hash = "sha256:b04a5941bf9c876d011c5defaf5a25be093d56e7270b8da52c9788b9df2a829a"}, ] [package.extras] @@ -2675,133 +2679,132 @@ dev = ["certifi", "mypy (>=1.14.1)", "pytest (>=8.1.1)", "pytest-asyncio (>=0.25 [[package]] name = "propcache" -version = "0.4.1" +version = "0.5.2" description = "Accelerated property cache" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, - {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, - {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"}, - {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"}, - {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"}, - {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"}, - {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"}, - {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"}, - {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"}, - {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"}, - {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"}, - {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"}, - {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"}, - {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"}, - {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"}, - {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"}, - {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"}, - {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}, - {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}, - {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}, - {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}, - {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}, - {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}, - {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183"}, - {file = "propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19"}, - {file = "propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f"}, - {file = "propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938"}, - {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}, - {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, + {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b"}, + {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c"}, + {file = "propcache-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bf3be92233808fcd338eba0fb4d0b59ec5772af4f4ecfcec450d1bfc0f8b5eb"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f8ea531c794b9d6274acd4e8d2c2ebcac590a4361d27482edd3010b79f1325e"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:decfca4c79dd53ebab484b00cc4b6717d8c369f86e74aa4ca395a64ac651495e"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4621064bbf28fa77ff64dd5d94367c04684c67d3a5bf1dff25f0cd0d98a38f3b"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b96db7141a592cbc968daf1feea83a118e6ab378af4abbc72b248c895414c22d"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ca071adabaab6e9219924bbe00af821f1ee7de113a9eca1cdc292de3d120f4d"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e4294d04a94dcab1b3bccd8b66d962dcad411a1d19414b2a41d1445f1de32ad0"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a0e399a2eccb91ed18721f86aa85757727400b6865c89e88934781deb9c8498b"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:823581fd5cb08b12a48bfa11fe962a7916766b6170c17b028fbdf762b85eb9bf"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:949c91d1a990cf3b2e8188dfcfb25005e0b834a06c63fa4ef9f360878ce21ecf"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:cc1177027eda740fdb152706bd215a3f124e3eea15afc39f2cb9fe351b50619e"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b05d643f944a8c3c4bd86d65ffd87bf3264b617f87791940302bc474d2ff5274"}, + {file = "propcache-0.5.2-cp310-cp310-win32.whl", hash = "sha256:8114f28879e0904748e831c3a7774261bd9e75f49be089f389a76f959dcd13fe"}, + {file = "propcache-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fcb98e7598b1ee0addab320d90f65b530297a867dbfe9de52ea838077e16e3d"}, + {file = "propcache-0.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:04dc2390d9edbbaef7461f33322555976ffddf0b650a038649d026358714e6c5"}, + {file = "propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78"}, + {file = "propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959"}, + {file = "propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f"}, + {file = "propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0"}, + {file = "propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82"}, + {file = "propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab"}, + {file = "propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba"}, + {file = "propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a"}, + {file = "propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a"}, + {file = "propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031"}, + {file = "propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42"}, + {file = "propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84"}, + {file = "propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a"}, + {file = "propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117"}, + {file = "propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4"}, + {file = "propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d"}, + {file = "propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757"}, + {file = "propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f"}, + {file = "propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d"}, + {file = "propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa"}, + {file = "propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568"}, + {file = "propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191"}, + {file = "propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7"}, + {file = "propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96"}, + {file = "propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999"}, + {file = "propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e"}, + {file = "propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4"}, + {file = "propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0"}, + {file = "propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c"}, + {file = "propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0"}, + {file = "propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb"}, + {file = "propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078"}, + {file = "propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2"}, + {file = "propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821"}, + {file = "propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370"}, + {file = "propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6"}, + {file = "propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe"}, + {file = "propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427"}, ] [[package]] @@ -2817,18 +2820,18 @@ files = [ [[package]] name = "pydantic" -version = "2.12.5" +version = "2.13.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" files = [ - {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, - {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, + {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"}, + {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.41.5" +pydantic-core = "2.46.4" typing-extensions = ">=4.14.1" typing-inspection = ">=0.4.2" @@ -2838,132 +2841,131 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.41.5" +version = "2.46.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" files = [ - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, - {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, + {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"}, + {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39"}, + {file = "pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d"}, + {file = "pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d"}, + {file = "pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb"}, + {file = "pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596"}, + {file = "pydantic_core-2.46.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae"}, + {file = "pydantic_core-2.46.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f444c499b3eefd3a92e348059471ea0c3a6e303d9c1cec09fa748fd9f895201"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3447661d99f75a3683a4cf5c87da72f2161964611864dbbeac7fbb118bb4bfc0"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b9bab013d1c7a79d3501ff86d0bc9c31bf587db4551677b96bec07df78c6b15"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d995260fdf4e1db774581b4900e0f832abe3c7c84996726bbc161b19c8f29e76"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13a646d65d09fbf1bc6b3a9635d30095c8e7e5cc419ff35ecc563c5fd04cd49"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432c179df7874eeb73307aad2df0755e1ae0efa61ff0ea89b93e194411ae3928"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:e68b7a074f65a2fd746c52a7ce6142ab7006074ac269ace0c25cd8ba171f8066"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a05d69cba51d852c5c3e92758653245a50c0b646ced0cf05bd793ed592839d6"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:228ee9bae8bef5b1e97ec58302f80357c37199e0d0a99174e138d28e6957b9d9"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:10e17cbb10a330363733efc4d7c4d0dd827ac0909b8f6a6542298fed1ea62f29"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:91a06d2e259ecfbd8c901d70c3c507900458498142b3026a296b7de4d1322cc9"}, + {file = "pydantic_core-2.46.4-cp39-cp39-win32.whl", hash = "sha256:d80ee3d731373b24cebbc10d689ca4ee1875caf0d5703a245db18efd4dd37fc1"}, + {file = "pydantic_core-2.46.4-cp39-cp39-win_amd64.whl", hash = "sha256:3be77f45df024d789a672ae34f8b06fb346c4f9f46ea714956660ea4862e89ac"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983"}, + {file = "pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1"}, ] [package.dependencies] @@ -2971,13 +2973,13 @@ typing-extensions = ">=4.14.1" [[package]] name = "pydantic-settings" -version = "2.12.0" +version = "2.15.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.10" files = [ - {file = "pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809"}, - {file = "pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0"}, + {file = "pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42"}, + {file = "pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117"}, ] [package.dependencies] @@ -2986,7 +2988,7 @@ python-dotenv = ">=0.21.0" typing-inspection = ">=0.4.0" [package.extras] -aws-secrets-manager = ["boto3 (>=1.35.0)", "boto3-stubs[secretsmanager]"] +aws-secrets-manager = ["boto3 (>=1.35.0)"] azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"] toml = ["tomli (>=2.0.1)"] @@ -2994,13 +2996,13 @@ yaml = ["pyyaml (>=6.0.1)"] [[package]] name = "pygithub" -version = "2.8.1" +version = "2.9.1" description = "Use the full Github API v3" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pygithub-2.8.1-py3-none-any.whl", hash = "sha256:23a0a5bca93baef082e03411bf0ce27204c32be8bfa7abc92fe4a3e132936df0"}, - {file = "pygithub-2.8.1.tar.gz", hash = "sha256:341b7c78521cb07324ff670afd1baa2bf5c286f8d9fd302c1798ba594a5400c9"}, + {file = "pygithub-2.9.1-py3-none-any.whl", hash = "sha256:2ec78fca30092d51a42d76f4ddb02131b6f0c666a35dfdf364cf302cdda115b9"}, + {file = "pygithub-2.9.1.tar.gz", hash = "sha256:59771d7ff63d54d427be2e7d0dad2208dfffc2b0a045fec959263787739b611c"}, ] [package.dependencies] @@ -3012,13 +3014,13 @@ urllib3 = ">=1.26.0" [[package]] name = "pygments" -version = "2.19.2" +version = "2.21.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, + {file = "pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9"}, + {file = "pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c"}, ] [package.extras] @@ -3026,13 +3028,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" -version = "2.11.0" +version = "2.13.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.9" files = [ - {file = "pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469"}, - {file = "pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623"}, + {file = "pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728"}, + {file = "pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423"}, ] [package.dependencies] @@ -3040,9 +3042,6 @@ cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"cryp [package.extras] crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==7.10.7)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=8.4.2,<9.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage[toml] (==7.10.7)", "pytest (>=8.4.2,<9.0.0)"] [[package]] name = "pynacl" @@ -3087,13 +3086,13 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=7.4.0)", "pytest-cov (>=2.10.1)", " [[package]] name = "pytest" -version = "9.0.2" +version = "9.1.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.10" files = [ - {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, - {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, + {file = "pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c"}, + {file = "pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313"}, ] [package.dependencies] @@ -3108,21 +3107,21 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests [[package]] name = "pytest-asyncio" -version = "1.3.0" +version = "1.4.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.10" files = [ - {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, - {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, + {file = "pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1"}, + {file = "pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42"}, ] [package.dependencies] -pytest = ">=8.2,<10" +pytest = ">=8.4,<10" typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} [package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)", "sphinx-tabs (>=3.5)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] @@ -3141,13 +3140,13 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.2.1" +version = "1.2.3" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61"}, - {file = "python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6"}, + {file = "python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9"}, + {file = "python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35"}, ] [package.extras] @@ -3155,13 +3154,13 @@ cli = ["click (>=5.0)"] [[package]] name = "python-gitlab" -version = "8.0.0" +version = "8.5.0" description = "The python wrapper for the GitLab REST and GraphQL APIs." optional = false python-versions = ">=3.10.0" files = [ - {file = "python_gitlab-8.0.0-py3-none-any.whl", hash = "sha256:c635e6722c5710d35ddadfcf95c362b0aa8de11ab3972bc4f230ebd58a6c49ee"}, - {file = "python_gitlab-8.0.0.tar.gz", hash = "sha256:03eae5a9d105448796e6c0e192d402c266057e75790cf4f42c143dddf91313ce"}, + {file = "python_gitlab-8.5.0-py3-none-any.whl", hash = "sha256:94228973c54f09eccd30f5160eca91200adc31d6ed0c894221a3865b90f96426"}, + {file = "python_gitlab-8.5.0.tar.gz", hash = "sha256:628529ec4ce1f9a7ba2c145b2cf5e4eeca3015418e504b2e6fba70171b6b1b59"}, ] [package.dependencies] @@ -3169,48 +3168,49 @@ requests = ">=2.32.0" requests-toolbelt = ">=1.0.0" [package.extras] -autocompletion = ["argcomplete (>=1.10.0,<3)"] +autocompletion = ["argcomplete (>=1.10.0,<4)"] graphql = ["gql[httpx] (>=3.5.0,<5)"] yaml = ["PyYaml (>=6.0.1)"] [[package]] name = "python-multipart" -version = "0.0.22" +version = "0.0.32" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.10" files = [ - {file = "python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155"}, - {file = "python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58"}, + {file = "python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23"}, + {file = "python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e"}, ] [[package]] name = "pywin32" -version = "311" -description = "Python for Window Extensions" +version = "312" +description = "Python for Windows Extensions" optional = false -python-versions = "*" +python-versions = ">=3.9" files = [ - {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, - {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, - {file = "pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b"}, - {file = "pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151"}, - {file = "pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503"}, - {file = "pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2"}, - {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"}, - {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"}, - {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"}, - {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"}, - {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"}, - {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"}, - {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"}, - {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"}, - {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"}, - {file = "pywin32-311-cp38-cp38-win32.whl", hash = "sha256:6c6f2969607b5023b0d9ce2541f8d2cbb01c4f46bc87456017cf63b73f1e2d8c"}, - {file = "pywin32-311-cp38-cp38-win_amd64.whl", hash = "sha256:c8015b09fb9a5e188f83b7b04de91ddca4658cee2ae6f3bc483f0b21a77ef6cd"}, - {file = "pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b"}, - {file = "pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91"}, - {file = "pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d"}, + {file = "pywin32-312-cp310-cp310-win32.whl", hash = "sha256:772235332b5d1024c696f11cea1ae4be7930f0a8b894bb43db14e3f435f1ff7e"}, + {file = "pywin32-312-cp310-cp310-win_amd64.whl", hash = "sha256:5dbc35d2b5320dc07f25fa31269cfb767471002b17de5eb067d03da68c7cb2db"}, + {file = "pywin32-312-cp310-cp310-win_arm64.whl", hash = "sha256:3020656e34f1cf7faeb7bccd2b84653a607c6ff0c55ada85e6487d61716deabd"}, + {file = "pywin32-312-cp311-cp311-win32.whl", hash = "sha256:17948aeadbdb091f0ced6ef0841620794e68327b94ee415571c1203594b7215c"}, + {file = "pywin32-312-cp311-cp311-win_amd64.whl", hash = "sha256:d11417d84412f859b722fad0841b3614459ed0047f7542d8362e77884f6b6e8a"}, + {file = "pywin32-312-cp311-cp311-win_arm64.whl", hash = "sha256:b2200a054ca6d6625c4842fc56a4976a4b47f96b73dbe5538c3f813a80359f47"}, + {file = "pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b"}, + {file = "pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc"}, + {file = "pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950"}, + {file = "pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c"}, + {file = "pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9"}, + {file = "pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831"}, + {file = "pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b"}, + {file = "pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e"}, + {file = "pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa"}, + {file = "pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed"}, + {file = "pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5"}, + {file = "pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9"}, + {file = "pywin32-312-cp39-cp39-win32.whl", hash = "sha256:d620900033cc7531e50727c3c8333091df5dd3ffe6d68cdca38c03f5821408d5"}, + {file = "pywin32-312-cp39-cp39-win_amd64.whl", hash = "sha256:dc90147579a905b8635e1b0ec6514967dcb07e6e0d9c42f1477feef14cac23bb"}, + {file = "pywin32-312-cp39-cp39-win_arm64.whl", hash = "sha256:02ebca0f0242b75292e218065004310d6a477407c09fa449bfe4f6022bc0c0fc"}, ] [[package]] @@ -3313,164 +3313,147 @@ typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "regex" -version = "2026.1.15" +version = "2026.7.19" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "regex-2026.1.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e3dd93c8f9abe8aa4b6c652016da9a3afa190df5ad822907efe6b206c09896e"}, - {file = "regex-2026.1.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:97499ff7862e868b1977107873dd1a06e151467129159a6ffd07b66706ba3a9f"}, - {file = "regex-2026.1.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bda75ebcac38d884240914c6c43d8ab5fb82e74cde6da94b43b17c411aa4c2b"}, - {file = "regex-2026.1.15-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7dcc02368585334f5bc81fc73a2a6a0bbade60e7d83da21cead622faf408f32c"}, - {file = "regex-2026.1.15-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:693b465171707bbe882a7a05de5e866f33c76aa449750bee94a8d90463533cc9"}, - {file = "regex-2026.1.15-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b0d190e6f013ea938623a58706d1469a62103fb2a241ce2873a9906e0386582c"}, - {file = "regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ff818702440a5878a81886f127b80127f5d50563753a28211482867f8318106"}, - {file = "regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f052d1be37ef35a54e394de66136e30fa1191fab64f71fc06ac7bc98c9a84618"}, - {file = "regex-2026.1.15-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6bfc31a37fd1592f0c4fc4bfc674b5c42e52efe45b4b7a6a14f334cca4bcebe4"}, - {file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d6ce5ae80066b319ae3bc62fd55a557c9491baa5efd0d355f0de08c4ba54e79"}, - {file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1704d204bd42b6bb80167df0e4554f35c255b579ba99616def38f69e14a5ccb9"}, - {file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e3174a5ed4171570dc8318afada56373aa9289eb6dc0d96cceb48e7358b0e220"}, - {file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:87adf5bd6d72e3e17c9cb59ac4096b1faaf84b7eb3037a5ffa61c4b4370f0f13"}, - {file = "regex-2026.1.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e85dc94595f4d766bd7d872a9de5ede1ca8d3063f3bdf1e2c725f5eb411159e3"}, - {file = "regex-2026.1.15-cp310-cp310-win32.whl", hash = "sha256:21ca32c28c30d5d65fc9886ff576fc9b59bbca08933e844fa2363e530f4c8218"}, - {file = "regex-2026.1.15-cp310-cp310-win_amd64.whl", hash = "sha256:3038a62fc7d6e5547b8915a3d927a0fbeef84cdbe0b1deb8c99bbd4a8961b52a"}, - {file = "regex-2026.1.15-cp310-cp310-win_arm64.whl", hash = "sha256:505831646c945e3e63552cc1b1b9b514f0e93232972a2d5bedbcc32f15bc82e3"}, - {file = "regex-2026.1.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ae6020fb311f68d753b7efa9d4b9a5d47a5d6466ea0d5e3b5a471a960ea6e4a"}, - {file = "regex-2026.1.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eddf73f41225942c1f994914742afa53dc0d01a6e20fe14b878a1b1edc74151f"}, - {file = "regex-2026.1.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e8cd52557603f5c66a548f69421310886b28b7066853089e1a71ee710e1cdc1"}, - {file = "regex-2026.1.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5170907244b14303edc5978f522f16c974f32d3aa92109fabc2af52411c9433b"}, - {file = "regex-2026.1.15-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2748c1ec0663580b4510bd89941a31560b4b439a0b428b49472a3d9944d11cd8"}, - {file = "regex-2026.1.15-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2f2775843ca49360508d080eaa87f94fa248e2c946bbcd963bb3aae14f333413"}, - {file = "regex-2026.1.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9ea2604370efc9a174c1b5dcc81784fb040044232150f7f33756049edfc9026"}, - {file = "regex-2026.1.15-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dcd31594264029b57bf16f37fd7248a70b3b764ed9e0839a8f271b2d22c0785"}, - {file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c08c1f3e34338256732bd6938747daa3c0d5b251e04b6e43b5813e94d503076e"}, - {file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e43a55f378df1e7a4fa3547c88d9a5a9b7113f653a66821bcea4718fe6c58763"}, - {file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f82110ab962a541737bd0ce87978d4c658f06e7591ba899192e2712a517badbb"}, - {file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:27618391db7bdaf87ac6c92b31e8f0dfb83a9de0075855152b720140bda177a2"}, - {file = "regex-2026.1.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bfb0d6be01fbae8d6655c8ca21b3b72458606c4aec9bbc932db758d47aba6db1"}, - {file = "regex-2026.1.15-cp311-cp311-win32.whl", hash = "sha256:b10e42a6de0e32559a92f2f8dc908478cc0fa02838d7dbe764c44dca3fa13569"}, - {file = "regex-2026.1.15-cp311-cp311-win_amd64.whl", hash = "sha256:e9bf3f0bbdb56633c07d7116ae60a576f846efdd86a8848f8d62b749e1209ca7"}, - {file = "regex-2026.1.15-cp311-cp311-win_arm64.whl", hash = "sha256:41aef6f953283291c4e4e6850607bd71502be67779586a61472beacb315c97ec"}, - {file = "regex-2026.1.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c8fcc5793dde01641a35905d6731ee1548f02b956815f8f1cab89e515a5bdf1"}, - {file = "regex-2026.1.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bfd876041a956e6a90ad7cdb3f6a630c07d491280bfeed4544053cd434901681"}, - {file = "regex-2026.1.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9250d087bc92b7d4899ccd5539a1b2334e44eee85d848c4c1aef8e221d3f8c8f"}, - {file = "regex-2026.1.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8a154cf6537ebbc110e24dabe53095e714245c272da9c1be05734bdad4a61aa"}, - {file = "regex-2026.1.15-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8050ba2e3ea1d8731a549e83c18d2f0999fbc99a5f6bd06b4c91449f55291804"}, - {file = "regex-2026.1.15-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf065240704cb8951cc04972cf107063917022511273e0969bdb34fc173456c"}, - {file = "regex-2026.1.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c32bef3e7aeee75746748643667668ef941d28b003bfc89994ecf09a10f7a1b5"}, - {file = "regex-2026.1.15-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5eaa4a4c5b1906bd0d2508d68927f15b81821f85092e06f1a34a4254b0e1af3"}, - {file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:86c1077a3cc60d453d4084d5b9649065f3bf1184e22992bd322e1f081d3117fb"}, - {file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2b091aefc05c78d286657cd4db95f2e6313375ff65dcf085e42e4c04d9c8d410"}, - {file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:57e7d17f59f9ebfa9667e6e5a1c0127b96b87cb9cede8335482451ed00788ba4"}, - {file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6c4dcdfff2c08509faa15d36ba7e5ef5fcfab25f1e8f85a0c8f45bc3a30725d"}, - {file = "regex-2026.1.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf8ff04c642716a7f2048713ddc6278c5fd41faa3b9cab12607c7abecd012c22"}, - {file = "regex-2026.1.15-cp312-cp312-win32.whl", hash = "sha256:82345326b1d8d56afbe41d881fdf62f1926d7264b2fc1537f99ae5da9aad7913"}, - {file = "regex-2026.1.15-cp312-cp312-win_amd64.whl", hash = "sha256:4def140aa6156bc64ee9912383d4038f3fdd18fee03a6f222abd4de6357ce42a"}, - {file = "regex-2026.1.15-cp312-cp312-win_arm64.whl", hash = "sha256:c6c565d9a6e1a8d783c1948937ffc377dd5771e83bd56de8317c450a954d2056"}, - {file = "regex-2026.1.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e69d0deeb977ffe7ed3d2e4439360089f9c3f217ada608f0f88ebd67afb6385e"}, - {file = "regex-2026.1.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3601ffb5375de85a16f407854d11cca8fe3f5febbe3ac78fb2866bb220c74d10"}, - {file = "regex-2026.1.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4c5ef43b5c2d4114eb8ea424bb8c9cec01d5d17f242af88b2448f5ee81caadbc"}, - {file = "regex-2026.1.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:968c14d4f03e10b2fd960f1d5168c1f0ac969381d3c1fcc973bc45fb06346599"}, - {file = "regex-2026.1.15-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56a5595d0f892f214609c9f76b41b7428bed439d98dc961efafdd1354d42baae"}, - {file = "regex-2026.1.15-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf650f26087363434c4e560011f8e4e738f6f3e029b85d4904c50135b86cfa5"}, - {file = "regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18388a62989c72ac24de75f1449d0fb0b04dfccd0a1a7c1c43af5eb503d890f6"}, - {file = "regex-2026.1.15-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d220a2517f5893f55daac983bfa9fe998a7dbcaee4f5d27a88500f8b7873788"}, - {file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9c08c2fbc6120e70abff5d7f28ffb4d969e14294fb2143b4b5c7d20e46d1714"}, - {file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ef7d5d4bd49ec7364315167a4134a015f61e8266c6d446fc116a9ac4456e10d"}, - {file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e42844ad64194fa08d5ccb75fe6a459b9b08e6d7296bd704460168d58a388f3"}, - {file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cfecdaa4b19f9ca534746eb3b55a5195d5c95b88cac32a205e981ec0a22b7d31"}, - {file = "regex-2026.1.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:08df9722d9b87834a3d701f3fca570b2be115654dbfd30179f30ab2f39d606d3"}, - {file = "regex-2026.1.15-cp313-cp313-win32.whl", hash = "sha256:d426616dae0967ca225ab12c22274eb816558f2f99ccb4a1d52ca92e8baf180f"}, - {file = "regex-2026.1.15-cp313-cp313-win_amd64.whl", hash = "sha256:febd38857b09867d3ed3f4f1af7d241c5c50362e25ef43034995b77a50df494e"}, - {file = "regex-2026.1.15-cp313-cp313-win_arm64.whl", hash = "sha256:8e32f7896f83774f91499d239e24cebfadbc07639c1494bb7213983842348337"}, - {file = "regex-2026.1.15-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ec94c04149b6a7b8120f9f44565722c7ae31b7a6d2275569d2eefa76b83da3be"}, - {file = "regex-2026.1.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40c86d8046915bb9aeb15d3f3f15b6fd500b8ea4485b30e1bbc799dab3fe29f8"}, - {file = "regex-2026.1.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:726ea4e727aba21643205edad8f2187ec682d3305d790f73b7a51c7587b64bdd"}, - {file = "regex-2026.1.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cb740d044aff31898804e7bf1181cc72c03d11dfd19932b9911ffc19a79070a"}, - {file = "regex-2026.1.15-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05d75a668e9ea16f832390d22131fe1e8acc8389a694c8febc3e340b0f810b93"}, - {file = "regex-2026.1.15-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d991483606f3dbec93287b9f35596f41aa2e92b7c2ebbb935b63f409e243c9af"}, - {file = "regex-2026.1.15-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:194312a14819d3e44628a44ed6fea6898fdbecb0550089d84c403475138d0a09"}, - {file = "regex-2026.1.15-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe2fda4110a3d0bc163c2e0664be44657431440722c5c5315c65155cab92f9e5"}, - {file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:124dc36c85d34ef2d9164da41a53c1c8c122cfb1f6e1ec377a1f27ee81deb794"}, - {file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1774cd1981cd212506a23a14dba7fdeaee259f5deba2df6229966d9911e767a"}, - {file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b5f7d8d2867152cdb625e72a530d2ccb48a3d199159144cbdd63870882fb6f80"}, - {file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:492534a0ab925d1db998defc3c302dae3616a2fc3fe2e08db1472348f096ddf2"}, - {file = "regex-2026.1.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c661fc820cfb33e166bf2450d3dadbda47c8d8981898adb9b6fe24e5e582ba60"}, - {file = "regex-2026.1.15-cp313-cp313t-win32.whl", hash = "sha256:99ad739c3686085e614bf77a508e26954ff1b8f14da0e3765ff7abbf7799f952"}, - {file = "regex-2026.1.15-cp313-cp313t-win_amd64.whl", hash = "sha256:32655d17905e7ff8ba5c764c43cb124e34a9245e45b83c22e81041e1071aee10"}, - {file = "regex-2026.1.15-cp313-cp313t-win_arm64.whl", hash = "sha256:b2a13dd6a95e95a489ca242319d18fc02e07ceb28fa9ad146385194d95b3c829"}, - {file = "regex-2026.1.15-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d920392a6b1f353f4aa54328c867fec3320fa50657e25f64abf17af054fc97ac"}, - {file = "regex-2026.1.15-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b5a28980a926fa810dbbed059547b02783952e2efd9c636412345232ddb87ff6"}, - {file = "regex-2026.1.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:621f73a07595d83f28952d7bd1e91e9d1ed7625fb7af0064d3516674ec93a2a2"}, - {file = "regex-2026.1.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d7d92495f47567a9b1669c51fc8d6d809821849063d168121ef801bbc213846"}, - {file = "regex-2026.1.15-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dd16fba2758db7a3780a051f245539c4451ca20910f5a5e6ea1c08d06d4a76b"}, - {file = "regex-2026.1.15-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1e1808471fbe44c1a63e5f577a1d5f02fe5d66031dcbdf12f093ffc1305a858e"}, - {file = "regex-2026.1.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0751a26ad39d4f2ade8fe16c59b2bf5cb19eb3d2cd543e709e583d559bd9efde"}, - {file = "regex-2026.1.15-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0c7684c7f9ca241344ff95a1de964f257a5251968484270e91c25a755532c5"}, - {file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:74f45d170a21df41508cb67165456538425185baaf686281fa210d7e729abc34"}, - {file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f1862739a1ffb50615c0fde6bae6569b5efbe08d98e59ce009f68a336f64da75"}, - {file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:453078802f1b9e2b7303fb79222c054cb18e76f7bdc220f7530fdc85d319f99e"}, - {file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:a30a68e89e5a218b8b23a52292924c1f4b245cb0c68d1cce9aec9bbda6e2c160"}, - {file = "regex-2026.1.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9479cae874c81bf610d72b85bb681a94c95722c127b55445285fb0e2c82db8e1"}, - {file = "regex-2026.1.15-cp314-cp314-win32.whl", hash = "sha256:d639a750223132afbfb8f429c60d9d318aeba03281a5f1ab49f877456448dcf1"}, - {file = "regex-2026.1.15-cp314-cp314-win_amd64.whl", hash = "sha256:4161d87f85fa831e31469bfd82c186923070fc970b9de75339b68f0c75b51903"}, - {file = "regex-2026.1.15-cp314-cp314-win_arm64.whl", hash = "sha256:91c5036ebb62663a6b3999bdd2e559fd8456d17e2b485bf509784cd31a8b1705"}, - {file = "regex-2026.1.15-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ee6854c9000a10938c79238de2379bea30c82e4925a371711af45387df35cab8"}, - {file = "regex-2026.1.15-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c2b80399a422348ce5de4fe40c418d6299a0fa2803dd61dc0b1a2f28e280fcf"}, - {file = "regex-2026.1.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dca3582bca82596609959ac39e12b7dad98385b4fefccb1151b937383cec547d"}, - {file = "regex-2026.1.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71d476caa6692eea743ae5ea23cde3260677f70122c4d258ca952e5c2d4e84"}, - {file = "regex-2026.1.15-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c243da3436354f4af6c3058a3f81a97d47ea52c9bd874b52fd30274853a1d5df"}, - {file = "regex-2026.1.15-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8355ad842a7c7e9e5e55653eade3b7d1885ba86f124dd8ab1f722f9be6627434"}, - {file = "regex-2026.1.15-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f192a831d9575271a22d804ff1a5355355723f94f31d9eef25f0d45a152fdc1a"}, - {file = "regex-2026.1.15-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:166551807ec20d47ceaeec380081f843e88c8949780cd42c40f18d16168bed10"}, - {file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9ca1cbdc0fbfe5e6e6f8221ef2309988db5bcede52443aeaee9a4ad555e0dac"}, - {file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b30bcbd1e1221783c721483953d9e4f3ab9c5d165aa709693d3f3946747b1aea"}, - {file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2a8d7b50c34578d0d3bf7ad58cde9652b7d683691876f83aedc002862a35dc5e"}, - {file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9d787e3310c6a6425eb346be4ff2ccf6eece63017916fd77fe8328c57be83521"}, - {file = "regex-2026.1.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:619843841e220adca114118533a574a9cd183ed8a28b85627d2844c500a2b0db"}, - {file = "regex-2026.1.15-cp314-cp314t-win32.whl", hash = "sha256:e90b8db97f6f2c97eb045b51a6b2c5ed69cedd8392459e0642d4199b94fabd7e"}, - {file = "regex-2026.1.15-cp314-cp314t-win_amd64.whl", hash = "sha256:5ef19071f4ac9f0834793af85bd04a920b4407715624e40cb7a0631a11137cdf"}, - {file = "regex-2026.1.15-cp314-cp314t-win_arm64.whl", hash = "sha256:ca89c5e596fc05b015f27561b3793dc2fa0917ea0d7507eebb448efd35274a70"}, - {file = "regex-2026.1.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:55b4ea996a8e4458dd7b584a2f89863b1655dd3d17b88b46cbb9becc495a0ec5"}, - {file = "regex-2026.1.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e1e28be779884189cdd57735e997f282b64fd7ccf6e2eef3e16e57d7a34a815"}, - {file = "regex-2026.1.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0057de9eaef45783ff69fa94ae9f0fd906d629d0bd4c3217048f46d1daa32e9b"}, - {file = "regex-2026.1.15-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc7cd0b2be0f0269283a45c0d8b2c35e149d1319dcb4a43c9c3689fa935c1ee6"}, - {file = "regex-2026.1.15-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8db052bbd981e1666f09e957f3790ed74080c2229007c1dd67afdbf0b469c48b"}, - {file = "regex-2026.1.15-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:343db82cb3712c31ddf720f097ef17c11dab2f67f7a3e7be976c4f82eba4e6df"}, - {file = "regex-2026.1.15-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:55e9d0118d97794367309635df398bdfd7c33b93e2fdfa0b239661cd74b4c14e"}, - {file = "regex-2026.1.15-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:008b185f235acd1e53787333e5690082e4f156c44c87d894f880056089e9bc7c"}, - {file = "regex-2026.1.15-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fd65af65e2aaf9474e468f9e571bd7b189e1df3a61caa59dcbabd0000e4ea839"}, - {file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f42e68301ff4afee63e365a5fc302b81bb8ba31af625a671d7acb19d10168a8c"}, - {file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f7792f27d3ee6e0244ea4697d92b825f9a329ab5230a78c1a68bd274e64b5077"}, - {file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:dbaf3c3c37ef190439981648ccbf0c02ed99ae066087dd117fcb616d80b010a4"}, - {file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:adc97a9077c2696501443d8ad3fa1b4fc6d131fc8fd7dfefd1a723f89071cf0a"}, - {file = "regex-2026.1.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:069f56a7bf71d286a6ff932a9e6fb878f151c998ebb2519a9f6d1cee4bffdba3"}, - {file = "regex-2026.1.15-cp39-cp39-win32.whl", hash = "sha256:ea4e6b3566127fda5e007e90a8fd5a4169f0cf0619506ed426db647f19c8454a"}, - {file = "regex-2026.1.15-cp39-cp39-win_amd64.whl", hash = "sha256:cda1ed70d2b264952e88adaa52eea653a33a1b98ac907ae2f86508eb44f65cdc"}, - {file = "regex-2026.1.15-cp39-cp39-win_arm64.whl", hash = "sha256:b325d4714c3c48277bfea1accd94e193ad6ed42b4bad79ad64f3b8f8a31260a5"}, - {file = "regex-2026.1.15.tar.gz", hash = "sha256:164759aa25575cbc0651bef59a0b18353e54300d79ace8084c818ad8ac72b7d5"}, + {file = "regex-2026.7.19-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:555497390743af1a65045fa4527782d10ff5b88970359412baa4a1e628fe393b"}, + {file = "regex-2026.7.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:343a4504e3fb688c47cad451221ca5d4814f42b1e16c0065bde9cbf7f473bd52"}, + {file = "regex-2026.7.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ebee1ee89c39c953baac6924fcde08c5bb427c4057510862f9d7c7bdb3d8665"}, + {file = "regex-2026.7.19-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:062f8cb7a9739c4835d22bd96f370c59aba89f257adcfa53be3cc209e08d3ae0"}, + {file = "regex-2026.7.19-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1123ef4211d763ee771d47916a1596e2f4915794f7aabdc1adcb20e4249a6951"}, + {file = "regex-2026.7.19-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6e44c0e7c5664be20aee92085153150c0a7967310a73a43c0f832b7cd35d0dd3"}, + {file = "regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98c6ac18480fcdb33f35439183f1d2e79760ab41930309c6d951cb1f8e46694c"}, + {file = "regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4458124d71339f505bf1fb94f69fd1bb8fa9d2481eebfef27c10ef4f2b9e12f6"}, + {file = "regex-2026.7.19-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbf300e2070bb35038660b3be1be4b91b0024edb41517e6996320b49b92b4175"}, + {file = "regex-2026.7.19-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b2b506b1788df5fecd270a10d5e70a95fe77b87ea2b370a318043f6f5f817ee6"}, + {file = "regex-2026.7.19-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:52579c60a6078be70a0e49c81d6e56d677f34cd439af281a0083b8c7bc75c095"}, + {file = "regex-2026.7.19-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:2955907b7157a6660f27079edf7e0229e9c9c5325c77a2ef6a890cba91efa6f0"}, + {file = "regex-2026.7.19-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:89dfee3319f5ae3f75ebd5c2445a809bb320252ba5529ffdafea4ef25d79cf1a"}, + {file = "regex-2026.7.19-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d3143f159261b1ce5b24c261c590e5913370c3200c5e9ebbb92b5aa5e111902"}, + {file = "regex-2026.7.19-cp310-cp310-win32.whl", hash = "sha256:64729333167c2dcaaa56a331d40ee097bd9c5617ffd51dabb09eaddafb1b532e"}, + {file = "regex-2026.7.19-cp310-cp310-win_amd64.whl", hash = "sha256:1c398716054621aa300b3d411f467dda903806c5da0df6945ab73982b8d115db"}, + {file = "regex-2026.7.19-cp310-cp310-win_arm64.whl", hash = "sha256:064f1760a5a4ade65c5419be23e782f29147528e8a66e0c42dd4cedb8d4e9fc6"}, + {file = "regex-2026.7.19-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac777001cdfc28b72477d93c8564bb7583081ea8fb45cdca3d568e0a4f87183c"}, + {file = "regex-2026.7.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:59787bd5f8c70aa339084e961d2996b53fbdeab4d5393bba5c1fe1fc32e02bae"}, + {file = "regex-2026.7.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:90c633e7e8d6bf4e992b8b36ce69e018f834b641dd6de8cea6d78c06ffa119c5"}, + {file = "regex-2026.7.19-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ccab0db8d5f4fbb0272642113c1adb2ffc698c16d3a0944580222331fa7a20"}, + {file = "regex-2026.7.19-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e50d748a32da622f256e8d505867f5d3c43a837c6a9f0efb149655fadd1042a"}, + {file = "regex-2026.7.19-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf1516fe58fc104f39b2d1dbe2d5e27d0cd45c4be2e42ba6ee0cc763701ec3c7"}, + {file = "regex-2026.7.19-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09f3e5287f94f17b709dc9a9e70865855feee835c861613be144218ce4ca82cc"}, + {file = "regex-2026.7.19-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6383cd2ed53a646c659ba1fe65727db76437fdaa069e697a0b44a51d5843d864"}, + {file = "regex-2026.7.19-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:09d3007fc76249a83cdd33de160d50e6cb77f54e09d8fa9e7148e10607ce24af"}, + {file = "regex-2026.7.19-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f8c6e7a1cfa3dc9d0ee2de0e65e834537fa29992cc3976ffec914afc35c5dd5"}, + {file = "regex-2026.7.19-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b2ea4a3e8357be8849e833beeae757ac3c7a6b3fc055c03c808a53c91ad30d82"}, + {file = "regex-2026.7.19-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:80115dd39481fd3a4b4080220799dbcacb921a844de4b827264ececacbe17c78"}, + {file = "regex-2026.7.19-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6ce43a0269d68cee79a7d1ade7def53c20f8f2a047b92d7b5d5bcc73ae88327"}, + {file = "regex-2026.7.19-cp311-cp311-win32.whl", hash = "sha256:9be2a6647740dd3cca6acb24e87f03d7632cd280dbce9bbe40c26353a215a45d"}, + {file = "regex-2026.7.19-cp311-cp311-win_amd64.whl", hash = "sha256:8d3469c91dd92ee41b7c95280edbd975ef1ba9195086686623a1c6e8935ce965"}, + {file = "regex-2026.7.19-cp311-cp311-win_arm64.whl", hash = "sha256:36aacfb15faaff3ced55afbf35ec72f50d4aee22082c4f7fe0573a33e2fca92e"}, + {file = "regex-2026.7.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2cc3460cedf7579948486eab03bc9ad7089df4d7281c0f47f4afe03e8d13f02d"}, + {file = "regex-2026.7.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0e9554c8785eac5cffe6300f69a91f58ba72bc88a5f8d661235ad7c6aa5b8ccd"}, + {file = "regex-2026.7.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7da47a0f248977f08e2cb659ff3c17ddc13a4d39b3a7baa0a81bf5b415430f6"}, + {file = "regex-2026.7.19-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93db40c8de0815baab96a06e08a984bac71f989d13bab789e382158c5d426797"}, + {file = "regex-2026.7.19-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:66bd62c59a5427746e8c44becae1d9b99d22fb13f30f492083dfb9ad7c45cc18"}, + {file = "regex-2026.7.19-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1649eb39fcc9ea80c4d2f110fde2b8ab2aef3877b98f02ab9b14e961f418c511"}, + {file = "regex-2026.7.19-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dce8ec9695f531a1b8a6f314fd4b393adcccf2ea861db480cdf97a301d01a68"}, + {file = "regex-2026.7.19-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3080a7fd38ef049bd489e01c970c97dd84ff446a885b0f1f6b26d9b1ad13ce11"}, + {file = "regex-2026.7.19-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d793a7988e04fcb1e2e135567443d82173225d657419ec09414a9b5a145b986"}, + {file = "regex-2026.7.19-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e8b0abe7d870f53ca5143895fef7d1041a0c831a140d3dc2c760dd7ba25d4a8b"}, + {file = "regex-2026.7.19-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4e5413bd5f13d3a4e3539ca98f70f75e7fca92518dd7f117f030ebedd10b60cb"}, + {file = "regex-2026.7.19-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:73b133a9e6fb512858e7f065e96f1180aa46646bc74a83aea62f1d314f3dd035"}, + {file = "regex-2026.7.19-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbe6493fbd27321b1d1f2dd4f5c7e5bd4d8b1d7cab7f32fd67db3d0b2ed8248a"}, + {file = "regex-2026.7.19-cp312-cp312-win32.whl", hash = "sha256:ddd67571c10869f65a5d7dde536d1e066e306cc90de57d7de4d5f34802428bb5"}, + {file = "regex-2026.7.19-cp312-cp312-win_amd64.whl", hash = "sha256:e30d40268a28d54ce0437031750497004c22602b8e3ab891f759b795a003b312"}, + {file = "regex-2026.7.19-cp312-cp312-win_arm64.whl", hash = "sha256:de9208bb427130c82a5dbfd104f92c8876fc9559278c880b3002755bbbe9c83d"}, + {file = "regex-2026.7.19-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f035d9dc1d25eff9d361456572231c7d27b5ccd473ca7dc0adfce732bd006d40"}, + {file = "regex-2026.7.19-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42572142ed0b9d5d261ba727157c426510da78e20828b66bbb855098b8a4e38"}, + {file = "regex-2026.7.19-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40b34dd88658e4fedd2fddbf0275ac970d00614b731357f425722a3ed1983d11"}, + {file = "regex-2026.7.19-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c41c63992bf1874cebb6e7f56fd7d3c007924659a604ae3d90e427d40d4fd13"}, + {file = "regex-2026.7.19-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d3372064506b94dd2c67c845f2db8062e9e9ba84d04e33cb96d7d33c11fe1ae"}, + {file = "regex-2026.7.19-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fce7760bf283405b2c7999cab3da4e72f7deca6396013115e3f7a955db9760da"}, + {file = "regex-2026.7.19-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0d702548d89d572b2929879bc883bb7a4c4709efafe4512cadee56c55c9bd15"}, + {file = "regex-2026.7.19-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d446c6ac40bb6e05025ccee55b84d80fe9bf8e93010ffc4bb9484f13d498835f"}, + {file = "regex-2026.7.19-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4c3501bfa814ab07b5580741f9bf78dfdfe146a04057f82df9e2402d2a975939"}, + {file = "regex-2026.7.19-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c4585c3e64b4f9e583b4d2683f18f5d5d872b3d71dcf24594b74ecc23602fa96"}, + {file = "regex-2026.7.19-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:571fde9741eb0ccde23dd4e0c1d50fbae910e901fa7e629faf39b2dda740d220"}, + {file = "regex-2026.7.19-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:15b364b9b98d6d2fe1a85034c23a3180ff913f46caddc3895f6fd65186255ccc"}, + {file = "regex-2026.7.19-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffd8893ccc1c2fce6e0d6ca402d716fe1b29db70c7132609a05955e31b2aa8f2"}, + {file = "regex-2026.7.19-cp313-cp313-win32.whl", hash = "sha256:f0fa4fa9c3632d708742baf2282f2055c11d888a790362670a403cbf48a2c404"}, + {file = "regex-2026.7.19-cp313-cp313-win_amd64.whl", hash = "sha256:d51ffd3427640fa2da6ade574ceba932f210ad095f65fcc450a2b0a0d454868e"}, + {file = "regex-2026.7.19-cp313-cp313-win_arm64.whl", hash = "sha256:c670fe7be5b6020b76bc6e8d2196074657e1327595bca93a389e1a76ab130ad8"}, + {file = "regex-2026.7.19-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db47b561c9afd884baa1f96f797c9ca369872c4b65912bc691cfa99e68340af2"}, + {file = "regex-2026.7.19-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65dcd28d3eba2ab7c2fd906485cc301392b47cc2234790d27d4e4814e02cdfda"}, + {file = "regex-2026.7.19-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f2e7f8e2ab6c2922be02c7ec45185aa5bd771e2e57b95455ee343a44d8130dff"}, + {file = "regex-2026.7.19-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe31f28c94402043161876a258a9c6f757cb485905c7614ce8d6cd40e6b7bdc1"}, + {file = "regex-2026.7.19-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8f6fa298bb4f7f58a33334406218ba74716e68feddf5e4e54cd5d8082705abf"}, + {file = "regex-2026.7.19-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cc1b2440423a851fad781309dd87843868f4f66a6bcd1ddb9225cf4ec2c84732"}, + {file = "regex-2026.7.19-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ac59a0900474a52b7c04af8196affc22bd9842acb0950df12f7b813e983609a"}, + {file = "regex-2026.7.19-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4896db1f4ce0576765b8272aa922df324e0f5b9bb2c3d03044ff32a7234a9aba"}, + {file = "regex-2026.7.19-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4e6883a021db30511d9fb8cfb0f222ce1f2c369f7d4d8b0448f449a93ba0bdfc"}, + {file = "regex-2026.7.19-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:09523a592938aa9f587fb74467c63ff0cf88fc3df14c82ab0f0517dcf76aaa62"}, + {file = "regex-2026.7.19-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1ebac3474b8589fce2f9b225b650afd61448f7c73a5d0255a10cc6366471aed1"}, + {file = "regex-2026.7.19-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4a0530bb1b8c1c985e7e2122e2b4d3aedd8a3c21c6bfddae6767c4405668b56e"}, + {file = "regex-2026.7.19-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef7eeb108c47ce7bcc9513e51bcb1bf57e8f483d52fce68a8642e3527141ae0"}, + {file = "regex-2026.7.19-cp313-cp313t-win32.whl", hash = "sha256:64b6ca7391a1395c2638dd5c7456d67bea44fc6c5e8e92c5dc8aa6a8f23292b4"}, + {file = "regex-2026.7.19-cp313-cp313t-win_amd64.whl", hash = "sha256:f04b9f56b0e0614c0126be12c2c2d9f8850c1e57af302bd0a63bed379d4af974"}, + {file = "regex-2026.7.19-cp313-cp313t-win_arm64.whl", hash = "sha256:fcee38cd8e5089d6d4f048ba1233b3ad76e5954f545382180889112ff5cb712d"}, + {file = "regex-2026.7.19-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a81758ed242b861b72e778ba34d41366441a2e10b16b472784c88da2dea7e2dd"}, + {file = "regex-2026.7.19-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4aa5435cdb3eb6f55fe98a171b05e3fbcd95fadaa4aa32acf62afd9b0cfdbcac"}, + {file = "regex-2026.7.19-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:60be8693a1dadc210bbcbc0db3e26da5f7d01d1d5a3da594e99b4fa42df404f5"}, + {file = "regex-2026.7.19-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19662dbedbe783d323196312d38f5ba53cf56296378252171985da6899887d3"}, + {file = "regex-2026.7.19-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d15df07081d91b76ff20d43f94592ee110330152d617b730fdbe5ef9fb680053"}, + {file = "regex-2026.7.19-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:56ad4d9f77df871a99e25c37091052a02528ec0eb059de928ee33956b854b45b"}, + {file = "regex-2026.7.19-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7322ec6cc9fba9d49ab888bb82d67ac5625627aa168f0165139b17018df3fb8a"}, + {file = "regex-2026.7.19-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9c7472192ebfad53a6be7c4a8bfb2d64b81c0e93a1fc8c57e1dd0b638297b5d1"}, + {file = "regex-2026.7.19-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c10b82c2634df08dfb13b1f04e38fe310d086ee092f4f69c0c8da234251e556e"}, + {file = "regex-2026.7.19-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:17ed5692f6acc4183e98331101a5f9e4f64d72fe58b753da4d444a2c77d05b12"}, + {file = "regex-2026.7.19-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:22a992de9a0d91bda927bf02b94351d737a0302905432c88a53de7c4b9ce62e2"}, + {file = "regex-2026.7.19-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:618a0aed532be87294c4477b0481f3aa0f1520f4014a4374dd4cf789b4cd2c97"}, + {file = "regex-2026.7.19-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ce9e679f776649746729b6c86382da519ef649c8e34cc41df0d2e5e0f6c36d4"}, + {file = "regex-2026.7.19-cp314-cp314-win32.whl", hash = "sha256:73f272fba87b8ccfe70a137d02a54af386f6d27aa509fbffdd978f5947aae1aa"}, + {file = "regex-2026.7.19-cp314-cp314-win_amd64.whl", hash = "sha256:d721e53758b2cca74990185eb0671dd466d7a388a1a45d0c6f4c13cef41a68ac"}, + {file = "regex-2026.7.19-cp314-cp314-win_arm64.whl", hash = "sha256:65fa6cb38ed5e9c3637e68e544f598b39c3b86b808ed0627a67b68320384b459"}, + {file = "regex-2026.7.19-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:5a2721c8720e2cb3c209925dfb9200199b4b07361c9e01d321719404b21458b3"}, + {file = "regex-2026.7.19-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:199535629f25caf89698039af3d1ad5fcae7f933e2112c73f1cdf49165c99518"}, + {file = "regex-2026.7.19-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9b60d7814174f059e5de4ab98271cc5ba9259cfea55273a81544dceea32dc8d9"}, + {file = "regex-2026.7.19-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbece16025afda5e3031af0c4059207e61dcf73ef13af844964f57f387d1c435"}, + {file = "regex-2026.7.19-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d24ecb4f5e009ea0bd275ee37ad9953b32005e2e5e60f8bbae16da0dbbf0d3a0"}, + {file = "regex-2026.7.19-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8cae6fd77a5b72dae505084b1a2ee0360139faf72fedbab667cd7cc65aae7a6a"}, + {file = "regex-2026.7.19-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9724e6cb5e478cd7d8cabf027826178739cb18cf0e117d0e32814d479fa02276"}, + {file = "regex-2026.7.19-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:572fc57b0009c735ee56c175ea021b637a15551a312f56734277f923d6fd0f6c"}, + {file = "regex-2026.7.19-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:20568e182eb82d39a6bf7cff3fd58566f14c75c6f74b2c8c96537eecf9010e3a"}, + {file = "regex-2026.7.19-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1d58561843f0ff7dc78b4c28b5e2dc388f3eff94ebc8a232a3adba961fc00009"}, + {file = "regex-2026.7.19-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:61bb1bd45520aacd56dd80943bd34991fb5350afdd1f36f2282230fd5154a218"}, + {file = "regex-2026.7.19-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:cd3584591ea4429026cdb931b054342c2bcf189b44ff367f8d5c15bc092a2966"}, + {file = "regex-2026.7.19-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cc26a66e212fa5d6c6170c3a40d99d888db3020c6fdab1523250d4341382e44"}, + {file = "regex-2026.7.19-cp314-cp314t-win32.whl", hash = "sha256:2c4e61e2e1be56f63ec3cc618aa9e0de81ef6f43d177205451840022e24f5b78"}, + {file = "regex-2026.7.19-cp314-cp314t-win_amd64.whl", hash = "sha256:c639ea314df70a7b2811e8020448c75af8c9445f5a60f8a4ced81c306a9380c2"}, + {file = "regex-2026.7.19-cp314-cp314t-win_arm64.whl", hash = "sha256:9a15e785f244f3e07847b984ce8773fc3da10a9f3c131cc49a4c5b4d672b4547"}, + {file = "regex-2026.7.19.tar.gz", hash = "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5"}, ] [[package]] name = "requests" -version = "2.32.5" +version = "2.34.2" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, - {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, + {file = "requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0"}, + {file = "requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed"}, ] [package.dependencies] -certifi = ">=2017.4.17" +certifi = ">=2023.5.7" charset_normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" +urllib3 = ">=1.26,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] [[package]] name = "requests-toolbelt" @@ -3488,13 +3471,13 @@ requests = ">=2.0.1,<3.0.0" [[package]] name = "rich" -version = "14.3.2" +version = "15.0.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.9.0" files = [ - {file = "rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69"}, - {file = "rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8"}, + {file = "rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb"}, + {file = "rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36"}, ] [package.dependencies] @@ -3506,165 +3489,165 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rpds-py" -version = "0.30.0" +version = "2026.6.3" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" files = [ - {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, - {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139"}, - {file = "rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464"}, - {file = "rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169"}, - {file = "rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425"}, - {file = "rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85"}, - {file = "rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c"}, - {file = "rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825"}, - {file = "rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229"}, - {file = "rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad"}, - {file = "rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394"}, - {file = "rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf"}, - {file = "rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b"}, - {file = "rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e"}, - {file = "rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2"}, - {file = "rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95"}, - {file = "rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d"}, - {file = "rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15"}, - {file = "rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1"}, - {file = "rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a"}, - {file = "rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27"}, - {file = "rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6"}, - {file = "rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d"}, - {file = "rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0"}, - {file = "rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53"}, - {file = "rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed"}, - {file = "rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950"}, - {file = "rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6"}, - {file = "rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb"}, - {file = "rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40"}, - {file = "rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0"}, - {file = "rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e"}, - {file = "rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84"}, + {file = "rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7"}, + {file = "rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da"}, + {file = "rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4"}, + {file = "rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6"}, + {file = "rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93"}, + {file = "rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a"}, + {file = "rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127"}, + {file = "rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804"}, + {file = "rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0"}, + {file = "rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4"}, + {file = "rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa"}, + {file = "rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc"}, + {file = "rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822"}, + {file = "rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed"}, + {file = "rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f"}, + {file = "rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96"}, + {file = "rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223"}, + {file = "rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4"}, + {file = "rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7"}, + {file = "rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d"}, + {file = "rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97"}, + {file = "rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0"}, + {file = "rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80"}, + {file = "rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb"}, + {file = "rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e"}, + {file = "rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77"}, + {file = "rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698"}, + {file = "rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd"}, + {file = "rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d"}, + {file = "rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8"}, + {file = "rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5"}, + {file = "rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2"}, + {file = "rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13"}, + {file = "rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868"}, + {file = "rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187"}, + {file = "rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107"}, + {file = "rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba"}, + {file = "rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369"}, + {file = "rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146"}, + {file = "rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826"}, + {file = "rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4"}, ] [[package]] name = "ruff" -version = "0.14.14" +version = "0.16.3" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed"}, - {file = "ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c"}, - {file = "ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de"}, - {file = "ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e"}, - {file = "ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8"}, - {file = "ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906"}, - {file = "ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480"}, - {file = "ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df"}, - {file = "ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b"}, - {file = "ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974"}, - {file = "ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66"}, - {file = "ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13"}, - {file = "ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412"}, - {file = "ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3"}, - {file = "ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b"}, - {file = "ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167"}, - {file = "ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd"}, - {file = "ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c"}, - {file = "ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b"}, + {file = "ruff-0.16.3-py3-none-linux_armv6l.whl", hash = "sha256:0c5710e247a58a4521e66e124ba9a74655b414f61ba3a2e9e3811e11098f48f7"}, + {file = "ruff-0.16.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fe155130631a2471fd2e14a7a664a4dfbd7194b8229c3d7b2a40b21178639081"}, + {file = "ruff-0.16.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e2ed719e14aa64d895c2ee922594a90a43c861a93f0575a95ff8c47cdbd13eb9"}, + {file = "ruff-0.16.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0b1da805eb043654645d74d5de1e5ce2edc686e40790d2b86f56d71cc06a84"}, + {file = "ruff-0.16.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a37bdea0bbe21780f590bf437d6412c8c4e1b6cd010f91a65c2c40c5e5f5f870"}, + {file = "ruff-0.16.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09571e6d1288ed9be475207a3ac04ada404f1cd898104be0f6ab8d7df438575b"}, + {file = "ruff-0.16.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c18c5a101eb540010638cc1ff3c84944d3adb3df62b8d98ca8f22ba484d3413"}, + {file = "ruff-0.16.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8457c44f15033c85ddbb77b15d451df9e24e4bd03b628396dd3610cedc3b8f82"}, + {file = "ruff-0.16.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:294b95c4ae0cda9388525c2047778aa758d6b8d4bb876fd4e9eaa3ebc92343eb"}, + {file = "ruff-0.16.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:3d0c7c40c87c2a820509c31ba007968da6e1306468c067b2d82fbfdbcd0e8474"}, + {file = "ruff-0.16.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9f738c0fdfa8eed0b2ce7fb27ee7258208a92a68d7949e62aa15164bc7b389da"}, + {file = "ruff-0.16.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fb785f0be25abe69d320415cd4f833b59e17ba7613d9ba6a958023b6bceb0a50"}, + {file = "ruff-0.16.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c5536e3acfbf9563085aa2be7b13c629c3077e902afc5b941ac44024dbb9f506"}, + {file = "ruff-0.16.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a2d85c02f9b8e165d85e6779184d38c4132de12603dab59c51c28e22584f9e4d"}, + {file = "ruff-0.16.3-py3-none-win32.whl", hash = "sha256:388cdf2166642bd9b13d52b5932d3170f34f8abed7e8d9a855f1d84b83645a0a"}, + {file = "ruff-0.16.3-py3-none-win_amd64.whl", hash = "sha256:e80a7d69ca2a6d1c4d352ec91458cdca6e56c83cdbcabd93e4abe1e53591d948"}, + {file = "ruff-0.16.3-py3-none-win_arm64.whl", hash = "sha256:b8ca152da82c1acc1fa8d5874b15951935f0eef46f10e6954c83859011b6178a"}, + {file = "ruff-0.16.3.tar.gz", hash = "sha256:e76d33a347661a84b5be6d043d0347fdc745dfdcf825a8f4fed64b5e26eebdf2"}, ] [[package]] name = "s3transfer" -version = "0.16.0" +version = "0.19.2" description = "An Amazon S3 Transfer Manager" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe"}, - {file = "s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920"}, + {file = "s3transfer-0.19.2-py3-none-any.whl", hash = "sha256:d8168eccca828cbb2cd573675333f3bddd254313a9c42494b84c76b539e8ba25"}, + {file = "s3transfer-0.19.2.tar.gz", hash = "sha256:ba0309fd86be3c27dbf78cdd813c13c5e1df16e5874b99d2535ebbdfb9892993"}, ] [package.dependencies] @@ -3697,13 +3680,13 @@ files = [ [[package]] name = "smmap" -version = "5.0.2" +version = "5.0.3" description = "A pure Python implementation of a sliding window memory map manager" optional = false python-versions = ">=3.7" files = [ - {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, - {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, + {file = "smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f"}, + {file = "smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c"}, ] [[package]] @@ -3730,121 +3713,24 @@ files = [ [[package]] name = "soupsieve" -version = "2.8.3" +version = "2.9.2" description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.9" -files = [ - {file = "soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95"}, - {file = "soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349"}, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.46" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" files = [ - {file = "sqlalchemy-2.0.46-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:895296687ad06dc9b11a024cf68e8d9d3943aa0b4964278d2553b86f1b267735"}, - {file = "sqlalchemy-2.0.46-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab65cb2885a9f80f979b85aa4e9c9165a31381ca322cbde7c638fe6eefd1ec39"}, - {file = "sqlalchemy-2.0.46-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52fe29b3817bd191cc20bad564237c808967972c97fa683c04b28ec8979ae36f"}, - {file = "sqlalchemy-2.0.46-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:09168817d6c19954d3b7655da6ba87fcb3a62bb575fb396a81a8b6a9fadfe8b5"}, - {file = "sqlalchemy-2.0.46-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:be6c0466b4c25b44c5d82b0426b5501de3c424d7a3220e86cd32f319ba56798e"}, - {file = "sqlalchemy-2.0.46-cp310-cp310-win32.whl", hash = "sha256:1bc3f601f0a818d27bfe139f6766487d9c88502062a2cd3a7ee6c342e81d5047"}, - {file = "sqlalchemy-2.0.46-cp310-cp310-win_amd64.whl", hash = "sha256:e0c05aff5c6b1bb5fb46a87e0f9d2f733f83ef6cbbbcd5c642b6c01678268061"}, - {file = "sqlalchemy-2.0.46-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:261c4b1f101b4a411154f1da2b76497d73abbfc42740029205d4d01fa1052684"}, - {file = "sqlalchemy-2.0.46-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:181903fe8c1b9082995325f1b2e84ac078b1189e2819380c2303a5f90e114a62"}, - {file = "sqlalchemy-2.0.46-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:590be24e20e2424a4c3c1b0835e9405fa3d0af5823a1a9fc02e5dff56471515f"}, - {file = "sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7568fe771f974abadce52669ef3a03150ff03186d8eb82613bc8adc435a03f01"}, - {file = "sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf7e1e78af38047e08836d33502c7a278915698b7c2145d045f780201679999"}, - {file = "sqlalchemy-2.0.46-cp311-cp311-win32.whl", hash = "sha256:9d80ea2ac519c364a7286e8d765d6cd08648f5b21ca855a8017d9871f075542d"}, - {file = "sqlalchemy-2.0.46-cp311-cp311-win_amd64.whl", hash = "sha256:585af6afe518732d9ccd3aea33af2edaae4a7aa881af5d8f6f4fe3a368699597"}, - {file = "sqlalchemy-2.0.46-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a9a72b0da8387f15d5810f1facca8f879de9b85af8c645138cba61ea147968c"}, - {file = "sqlalchemy-2.0.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2347c3f0efc4de367ba00218e0ae5c4ba2306e47216ef80d6e31761ac97cb0b9"}, - {file = "sqlalchemy-2.0.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9094c8b3197db12aa6f05c51c05daaad0a92b8c9af5388569847b03b1007fb1b"}, - {file = "sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37fee2164cf21417478b6a906adc1a91d69ae9aba8f9533e67ce882f4bb1de53"}, - {file = "sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1e14b2f6965a685c7128bd315e27387205429c2e339eeec55cb75ca4ab0ea2e"}, - {file = "sqlalchemy-2.0.46-cp312-cp312-win32.whl", hash = "sha256:412f26bb4ba942d52016edc8d12fb15d91d3cd46b0047ba46e424213ad407bcb"}, - {file = "sqlalchemy-2.0.46-cp312-cp312-win_amd64.whl", hash = "sha256:ea3cd46b6713a10216323cda3333514944e510aa691c945334713fca6b5279ff"}, - {file = "sqlalchemy-2.0.46-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a12da97cca70cea10d4b4fc602589c4511f96c1f8f6c11817620c021d21d00"}, - {file = "sqlalchemy-2.0.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af865c18752d416798dae13f83f38927c52f085c52e2f32b8ab0fef46fdd02c2"}, - {file = "sqlalchemy-2.0.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d679b5f318423eacb61f933a9a0f75535bfca7056daeadbf6bd5bcee6183aee"}, - {file = "sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64901e08c33462acc9ec3bad27fc7a5c2b6491665f2aa57564e57a4f5d7c52ad"}, - {file = "sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8ac45e8f4eaac0f9f8043ea0e224158855c6a4329fd4ee37c45c61e3beb518e"}, - {file = "sqlalchemy-2.0.46-cp313-cp313-win32.whl", hash = "sha256:8d3b44b3d0ab2f1319d71d9863d76eeb46766f8cf9e921ac293511804d39813f"}, - {file = "sqlalchemy-2.0.46-cp313-cp313-win_amd64.whl", hash = "sha256:77f8071d8fbcbb2dd11b7fd40dedd04e8ebe2eb80497916efedba844298065ef"}, - {file = "sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1e8cc6cc01da346dc92d9509a63033b9b1bda4fed7a7a7807ed385c7dccdc10"}, - {file = "sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96c7cca1a4babaaf3bfff3e4e606e38578856917e52f0384635a95b226c87764"}, - {file = "sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2a9f9aee38039cf4755891a1e50e1effcc42ea6ba053743f452c372c3152b1b"}, - {file = "sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db23b1bf8cfe1f7fda19018e7207b20cdb5168f83c437ff7e95d19e39289c447"}, - {file = "sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada"}, - {file = "sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366"}, - {file = "sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d"}, - {file = "sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e"}, - {file = "sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf"}, - {file = "sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908"}, - {file = "sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b"}, - {file = "sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa"}, - {file = "sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863"}, - {file = "sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede"}, - {file = "sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330"}, - {file = "sqlalchemy-2.0.46-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6ac245604295b521de49b465bab845e3afe6916bcb2147e5929c8041b4ec0545"}, - {file = "sqlalchemy-2.0.46-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e6199143d51e3e1168bedd98cc698397404a8f7508831b81b6a29b18b051069"}, - {file = "sqlalchemy-2.0.46-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:716be5bcabf327b6d5d265dbdc6213a01199be587224eb991ad0d37e83d728fd"}, - {file = "sqlalchemy-2.0.46-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6f827fd687fa1ba7f51699e1132129eac8db8003695513fcf13fc587e1bd47a5"}, - {file = "sqlalchemy-2.0.46-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c805fa6e5d461329fa02f53f88c914d189ea771b6821083937e79550bf31fc19"}, - {file = "sqlalchemy-2.0.46-cp38-cp38-win32.whl", hash = "sha256:3aac08f7546179889c62b53b18ebf1148b10244b3405569c93984b0388d016a7"}, - {file = "sqlalchemy-2.0.46-cp38-cp38-win_amd64.whl", hash = "sha256:0cc3117db526cad3e61074100bd2867b533e2c7dc1569e95c14089735d6fb4fe"}, - {file = "sqlalchemy-2.0.46-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:90bde6c6b1827565a95fde597da001212ab436f1b2e0c2dcc7246e14db26e2a3"}, - {file = "sqlalchemy-2.0.46-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b1e5f3a5f1ff4f42d5daab047428cd45a3380e51e191360a35cef71c9a7a2a"}, - {file = "sqlalchemy-2.0.46-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93bb0aae40b52c57fd74ef9c6933c08c040ba98daf23ad33c3f9893494b8d3ce"}, - {file = "sqlalchemy-2.0.46-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c4e2cc868b7b5208aec6c960950b7bb821f82c2fe66446c92ee0a571765e91a5"}, - {file = "sqlalchemy-2.0.46-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:965c62be8256d10c11f8907e7a8d3e18127a4c527a5919d85fa87fd9ecc2cfdc"}, - {file = "sqlalchemy-2.0.46-cp39-cp39-win32.whl", hash = "sha256:9397b381dcee8a2d6b99447ae85ea2530dcac82ca494d1db877087a13e38926d"}, - {file = "sqlalchemy-2.0.46-cp39-cp39-win_amd64.whl", hash = "sha256:4396c948d8217e83e2c202fbdcc0389cf8c93d2c1c5e60fa5c5a955eae0e64be"}, - {file = "sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e"}, - {file = "sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7"}, + {file = "soupsieve-2.9.2-py3-none-any.whl", hash = "sha256:8089a26fd974ca7a1f30276d3d8492ab266ab15af581642dfe8aa162e0c1c823"}, + {file = "soupsieve-2.9.2.tar.gz", hash = "sha256:4a55d8cf158a9c2e587fa4922f1bbb91d68ac829e2d6f25403a85747c71daf74"}, ] -[package.dependencies] -greenlet = {version = ">=1", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"] -aioodbc = ["aioodbc", "greenlet (>=1)"] -aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (>=1)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - [[package]] name = "sse-starlette" -version = "3.2.0" +version = "3.4.8" description = "SSE plugin for Starlette" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "sse_starlette-3.2.0-py3-none-any.whl", hash = "sha256:5876954bd51920fc2cd51baee47a080eb88a37b5b784e615abb0b283f801cdbf"}, - {file = "sse_starlette-3.2.0.tar.gz", hash = "sha256:8127594edfb51abe44eac9c49e59b0b01f1039d0c7461c6fd91d4e03b70da422"}, + {file = "sse_starlette-3.4.8-py3-none-any.whl", hash = "sha256:6e82314c786709a3cd9520f2285cf9fff90e181e598e8a357b0cf80f66afba0d"}, + {file = "sse_starlette-3.4.8.tar.gz", hash = "sha256:ed89ffbb75cbf78a5fe2f2109cd584792ee7f9dfac96f791db546df8f15f3f9c"}, ] [package.dependencies] @@ -3853,19 +3739,20 @@ starlette = ">=0.49.1" [package.extras] daphne = ["daphne (>=4.2.0)"] -examples = ["aiosqlite (>=0.21.0)", "fastapi (>=0.115.12)", "sqlalchemy[asyncio] (>=2.0.41)", "uvicorn (>=0.34.0)"] +examples = ["fastapi (>=0.115.12)", "pydantic (>=2)", "uvicorn (>=0.34.0)"] +examples-db = ["aiosqlite (>=0.21.0)", "sqlalchemy[asyncio] (>=2.0.41)"] granian = ["granian (>=2.3.1)"] uvicorn = ["uvicorn (>=0.34.0)"] [[package]] name = "starlette" -version = "0.52.1" +version = "1.6.0" description = "The little ASGI library that shines." optional = false python-versions = ">=3.10" files = [ - {file = "starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74"}, - {file = "starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933"}, + {file = "starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c"}, + {file = "starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b"}, ] [package.dependencies] @@ -3873,17 +3760,17 @@ anyio = ">=3.6.2,<5" typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} [package.extras] -full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] +full = ["httpx (>=0.27.0,<0.29.0)", "httpx2 (>=2.0.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] [[package]] name = "tenacity" -version = "9.1.2" +version = "9.1.4" description = "Retry code until it succeeds" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, - {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, + {file = "tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55"}, + {file = "tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a"}, ] [package.extras] @@ -3892,108 +3779,101 @@ test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "tiktoken" -version = "0.12.0" +version = "0.13.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" files = [ - {file = "tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970"}, - {file = "tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16"}, - {file = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030"}, - {file = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134"}, - {file = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a"}, - {file = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892"}, - {file = "tiktoken-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1"}, - {file = "tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb"}, - {file = "tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa"}, - {file = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc"}, - {file = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded"}, - {file = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd"}, - {file = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967"}, - {file = "tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def"}, - {file = "tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8"}, - {file = "tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b"}, - {file = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37"}, - {file = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad"}, - {file = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5"}, - {file = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3"}, - {file = "tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd"}, - {file = "tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3"}, - {file = "tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160"}, - {file = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa"}, - {file = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be"}, - {file = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a"}, - {file = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3"}, - {file = "tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697"}, - {file = "tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16"}, - {file = "tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a"}, - {file = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27"}, - {file = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb"}, - {file = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e"}, - {file = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25"}, - {file = "tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f"}, - {file = "tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646"}, - {file = "tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88"}, - {file = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff"}, - {file = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830"}, - {file = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b"}, - {file = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b"}, - {file = "tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3"}, - {file = "tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365"}, - {file = "tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e"}, - {file = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63"}, - {file = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0"}, - {file = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a"}, - {file = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0"}, - {file = "tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71"}, - {file = "tiktoken-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d51d75a5bffbf26f86554d28e78bfb921eae998edc2675650fd04c7e1f0cdc1e"}, - {file = "tiktoken-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:09eb4eae62ae7e4c62364d9ec3a57c62eea707ac9a2b2c5d6bd05de6724ea179"}, - {file = "tiktoken-0.12.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:df37684ace87d10895acb44b7f447d4700349b12197a526da0d4a4149fde074c"}, - {file = "tiktoken-0.12.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:4c9614597ac94bb294544345ad8cf30dac2129c05e2db8dc53e082f355857af7"}, - {file = "tiktoken-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:20cf97135c9a50de0b157879c3c4accbb29116bcf001283d26e073ff3b345946"}, - {file = "tiktoken-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:15d875454bbaa3728be39880ddd11a5a2a9e548c29418b41e8fd8a767172b5ec"}, - {file = "tiktoken-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cff3688ba3c639ebe816f8d58ffbbb0aa7433e23e08ab1cade5d175fc973fb3"}, - {file = "tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931"}, + {file = "tiktoken-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:47b1df8d73390a24f94980c75158cdd5c56d256f16d55f30cb49c230caba9ba4"}, + {file = "tiktoken-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7d40c6c5aab171dcd6eb8455bc567bde404bb9def60cdb8c1299cc782b242bb9"}, + {file = "tiktoken-0.13.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:9b842981fa91accdffd48ff6408a977b7a91c3fbda55d353c3c68114d5c9d69e"}, + {file = "tiktoken-0.13.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ed5a30027cb4d8c7ca8b273d4766f3db3cf58fad9e9f3b1a68a351ffb54873d5"}, + {file = "tiktoken-0.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7ab10f4a21c2999846940113f6dbd72e0fa06a24119feddd74cc47e85818e06d"}, + {file = "tiktoken-0.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a2937ad042d49d50eac6e1ba07c5661d4bd3942a5b1e0c0d08475c4df83676e1"}, + {file = "tiktoken-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:44733b99bfd72b590cd0936b1c01b3b4dd73122db2d544bc1ceeb18a7678c910"}, + {file = "tiktoken-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7bfe1849caa65d1e1d9871817170ec497bbb7984e182012e1bdce72f66608cdb"}, + {file = "tiktoken-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:91c180fe255bd5a86d8316210d2833a1d4d33d026cd86a67812f4773743c8d26"}, + {file = "tiktoken-0.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:059c8ecf554eb5b41e6e054ba467b871b03277d267dee7244380aca4359747d4"}, + {file = "tiktoken-0.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:36217497eaffc158607a3b26f065300db2aefd43b115263f3b9688ce38146173"}, + {file = "tiktoken-0.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:303f7d91b4fce3baddbcde05c139091d4caa5026ac7214c1dc7ff7a71ee429ff"}, + {file = "tiktoken-0.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5d48843bee149630eb735a99e1f4a85b47308d21868ea63163f6e87768d3cfed"}, + {file = "tiktoken-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:fc1c44cd37b43fc46bae593129164f4f281e82ea116b57a85aa81bda57eafc94"}, + {file = "tiktoken-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32ac870a806cfb260a02d0cb70426aef02e038297f8ad50df5040bb5af360791"}, + {file = "tiktoken-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d9980f11429ed2d737c463bb1fb78cf330caa026adf002f714aced7849a687b"}, + {file = "tiktoken-0.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3f277ebea5edd7b8bf03c6f9431e1d67d517530115572b2dc1d465326e8f88c7"}, + {file = "tiktoken-0.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:a116178fa7e1b4065bff05214360373a65cac22f965be7b3f73d00a0dbfe7649"}, + {file = "tiktoken-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c397ddda233208345b01bd30f2fca79ff730e55731d0108a603f9bc57f6af3b"}, + {file = "tiktoken-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95097e4f89b06403976e498abf61a0ee73a7497e73fb599cb211d8197a054d91"}, + {file = "tiktoken-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:8f2d16e7a7c783ad81f36e457d046d1f1c8af70b22aec8a13238efe531977c41"}, + {file = "tiktoken-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5df5d1507bd245f1ccad4a074698240021239e455eb0bb4ced4e3d7181872154"}, + {file = "tiktoken-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fe806a50664e83a6ffd56cbd1e4f5dcc6cd32a3e7538f70dc38b1a271384545"}, + {file = "tiktoken-0.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:125bc05005e747f993a83dc67934249932d6e4209854452cd4c0b1d53fba3ba2"}, + {file = "tiktoken-0.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5e6358911cab4adee6712da27d65573496a4f68cf8a2b5fca6a4ad10fc5748cf"}, + {file = "tiktoken-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:975cbd78d085d75d26b59660e262736dcaed1e35f8f142cd6291025c01d25486"}, + {file = "tiktoken-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ab9bc99fa020a4c283424590ecd7f3afd70c1c281cb3fa3192a6c3af9f9615"}, + {file = "tiktoken-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:6b1615f0ff71953d19729ceb18865429c185b0a23c5353f1bbca34a394bf60f7"}, + {file = "tiktoken-0.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6eb4a5bfbc6426938026b1a334e898ac53541360d62d8c689870160cc80abd67"}, + {file = "tiktoken-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:43cee3e5400573b2046fbf092cc7a5bc30164f9e4c95ce20714da929df48737a"}, + {file = "tiktoken-0.13.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:7de52e3f566d19b3b11bd37eea552c6c305ad74081f736882bd44d148ed4c48d"}, + {file = "tiktoken-0.13.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:51384448aa508e4df84c0f7c1dc3211c7f7b8096325660ee5fc82f3e11b381ce"}, + {file = "tiktoken-0.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e28157350f7ebf35008dd8e9e0fdb621f976e4230c881099c85e8cf07eaa50e2"}, + {file = "tiktoken-0.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:165cf1820ea4a354985c2490a5205d4cc74661c934aca79dd0368232fff94e0f"}, + {file = "tiktoken-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6c43a675ca14f6f2749ba7f12075d37456015a24b859f2517b9beb4ef30807ec"}, + {file = "tiktoken-0.13.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaaaef47c2406277181d2086484c317bf7fc433e2d5d03ff94f56b0dcec87471"}, + {file = "tiktoken-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ca8b310bd93b3772cb1b7922d915446864860f562bdfe4825c63a0aed3fb28cd"}, + {file = "tiktoken-0.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:32e0c12305105002c047b3bb1070b0dd9a73b0cb3b2856a8972b810e7a4f5881"}, + {file = "tiktoken-0.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:5ba5fd62507a932d1241346179e3b39bc7bf7408f03c272652d93b3bedf5db24"}, + {file = "tiktoken-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d108bc2d470fc53c8ecd24f2c0fd2b5f98c33e87cdb6aa2e9b8c5dced703d273"}, + {file = "tiktoken-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb99cb5127449f58d0a2d5f5ccfb390d8dbdfd919c221246caaee29d8725ed51"}, + {file = "tiktoken-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:115c4f26ffa11caac8b54eea35c2ad38c612c20a48d35dd15d70a02ac6f51f58"}, + {file = "tiktoken-0.13.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:472527e9132952f2fbf77cd290658bacf003d4d5a3fabc18e5fbd407cbae4d9b"}, + {file = "tiktoken-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e2f67d27c9626cdd25fe33d9313c5cdb3d8d82da646b68d6eb8e7e9c20e6448"}, + {file = "tiktoken-0.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2b920b35805cd64585a37c3dc7ce65fba4d2d36016be01e1d7942482ca29093a"}, + {file = "tiktoken-0.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:493af3aa28a4aaf2e3d2600a2ee717252c9bf5ab38fff94eb5a02db5ab77e5ad"}, + {file = "tiktoken-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6644c9c2b5cf3916f5a3641d7d12fdb3f006a7b3d9ff6acdaec44e29ab1ff91e"}, + {file = "tiktoken-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cb65b60b9408563676d874a3a4ee573370066f0dc4e29d84e82e989c6517424"}, + {file = "tiktoken-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:85b78cc3a2c3d48723ca751fa981f1fedccd54194ca0471b957364353a898b07"}, + {file = "tiktoken-0.13.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:35e1ea1e0631c04f551297284a1ab7e1f65a3c55a9a48728d5e0f66b4527c04a"}, + {file = "tiktoken-0.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2a3b536c55802fe42f4b4644d2be4f04bf788506b48de0a0a658cb58f8bce232"}, + {file = "tiktoken-0.13.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:b8ac2d6420ff05841a89ba5205c6d45f56c4f6843454f3c884b7eb1a2a8dddb2"}, + {file = "tiktoken-0.13.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:477c9a38e20d0ed248090509acf1e839ad3967a4f00b4b0f958210049f656dee"}, + {file = "tiktoken-0.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:da86f8c96ac1c235d7a3b3eebff1eacfdbcfb8ad792706943268d4d2938fbafe"}, + {file = "tiktoken-0.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9b8858b29804b3a0add25ce9e62fb00f89f621dc754d75d03ca419d17e8ddf67"}, + {file = "tiktoken-0.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:b967dfb9d0adf9a631953b1b40717684f04478270fc51bbccdd2f838d67a2f00"}, + {file = "tiktoken-0.13.0.tar.gz", hash = "sha256:c9435714c3a84c2319499de9a300c0e604449dd0799ff246458b3bb6a7f433c1"}, ] [package.dependencies] -regex = ">=2022.1.18" -requests = ">=2.26.0" +regex = "*" +requests = "*" [package.extras] -blobfile = ["blobfile (>=2)"] +blobfile = ["blobfile (>=3)"] [[package]] name = "tokenizers" -version = "0.22.2" +version = "0.23.1" description = "" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c"}, - {file = "tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001"}, - {file = "tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7"}, - {file = "tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd"}, - {file = "tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5"}, - {file = "tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e"}, - {file = "tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b"}, - {file = "tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67"}, - {file = "tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4"}, - {file = "tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a"}, - {file = "tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a"}, - {file = "tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5"}, - {file = "tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92"}, - {file = "tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48"}, - {file = "tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc"}, - {file = "tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4"}, - {file = "tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c"}, - {file = "tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195"}, - {file = "tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5"}, - {file = "tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:319f659ee992222f04e58f84cbf407cfa66a65fe3a8de44e8ad2bc53e7d99012"}, - {file = "tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e50f8554d504f617d9e9d6e4c2c2884a12b388a97c5c77f0bc6cf4cd032feee"}, - {file = "tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a62ba2c5faa2dd175aaeed7b15abf18d20266189fb3406c5d0550dd34dd5f37"}, - {file = "tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143b999bdc46d10febb15cbffb4207ddd1f410e2c755857b5a0797961bbdc113"}, - {file = "tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917"}, + {file = "tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e03d6ffcbe0d56ee9c1ccd070e70a13fa750727c0277e138152acbc0252c2224"}, + {file = "tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0948bbb1ac1d7cdfc9fb6d62c596e3b7550036ad60ecd654a66ad273326324e"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf13402aff9bc533c89cb849ec3b412dc3fbeacc9744840e423d7bf3f7dc0e3"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f836ca703b89ae07919a309f9651f7a88fd5a33d5f718ba5ad0870ec0256bad6"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae848657742035523fdf261773630cb819a26995fcd3d9ecae0c1daf6e5a4959"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b09e85775d5187941e7bab30e941b4134ab4a7dd8c68e783d231fb7ca27c51"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea5a0ce170074329faaa8ea3f6400ecde604b6678192688533af80980daae71a"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b405006415ea148a992d093699c66eb01952bf59f4d5727089a98bda45a4"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:56f3a77de629917652f876294dc9fe6bad4a0c43bc229dc72e59bb23a0f4729a"}, + {file = "tokenizers-0.23.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d10a6d957ef01896dc274e890eee27d41bd0e74ef31e60616f0fc311345184e"}, + {file = "tokenizers-0.23.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1974288a609c343774f1b897c8b482c791ab17b75ab5c8c2b1737565c1d82288"}, + {file = "tokenizers-0.23.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:120468fb4c24faf0543c835a4fabafa4deb3f20a035c9b6e83d0b553a97615d4"}, + {file = "tokenizers-0.23.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e3d8f40ea6268047de7046906326abed5134f27d4e8447b23763afe5808c8a96"}, + {file = "tokenizers-0.23.1-cp310-abi3-win32.whl", hash = "sha256:93120a930b919416da7cd10a2f606ac9919cc69cacae7980fa2140e277660948"}, + {file = "tokenizers-0.23.1-cp310-abi3-win_amd64.whl", hash = "sha256:e7bfaf995c1bdbbd21d13539decb6650967013759318627d85daeb7881af16b7"}, + {file = "tokenizers-0.23.1-cp310-abi3-win_arm64.whl", hash = "sha256:a26197957d8e4425dfba746315f3c425ea00cfa8367c5fbc4ec73447893dcea9"}, + {file = "tokenizers-0.23.1.tar.gz", hash = "sha256:1feeeadf865a7915adc25445dea30e9933e593c31bb96c277cee36de227c8bfa"}, ] [package.dependencies] @@ -4006,73 +3886,77 @@ testing = ["datasets", "numpy", "pytest", "pytest-asyncio", "requests", "ruff", [[package]] name = "tqdm" -version = "4.67.2" +version = "4.70.0" description = "Fast, Extensible Progress Meter" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tqdm-4.67.2-py3-none-any.whl", hash = "sha256:9a12abcbbff58b6036b2167d9d3853042b9d436fe7330f06ae047867f2f8e0a7"}, - {file = "tqdm-4.67.2.tar.gz", hash = "sha256:649aac53964b2cb8dec76a14b405a4c0d13612cb8933aae547dd144eacc99653"}, + {file = "tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953"}, + {file = "tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] -discord = ["requests"] +discord = ["envwrap", "requests"] notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] +slack = ["envwrap", "slack-sdk"] +telegram = ["envwrap", "requests"] [[package]] name = "tree-sitter" -version = "0.25.2" +version = "0.26.0" description = "Python bindings to the Tree-sitter parsing library" optional = false python-versions = ">=3.10" files = [ - {file = "tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20"}, - {file = "tree_sitter-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72a510931c3c25f134aac2daf4eb4feca99ffe37a35896d7150e50ac3eee06c7"}, - {file = "tree_sitter-0.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44488e0e78146f87baaa009736886516779253d6d6bac3ef636ede72bc6a8234"}, - {file = "tree_sitter-0.25.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2f8e7d6b2f8489d4a9885e3adcaef4bc5ff0a275acd990f120e29c4ab3395c5"}, - {file = "tree_sitter-0.25.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b570690f87f1da424cd690e51cc56728d21d63f4abd4b326d382a30353acc7"}, - {file = "tree_sitter-0.25.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a0ec41b895da717bc218a42a3a7a0bfcfe9a213d7afaa4255353901e0e21f696"}, - {file = "tree_sitter-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:7712335855b2307a21ae86efe949c76be36c6068d76df34faa27ce9ee40ff444"}, - {file = "tree_sitter-0.25.2-cp310-cp310-win_arm64.whl", hash = "sha256:a925364eb7fbb9cdce55a9868f7525a1905af512a559303bd54ef468fd88cb37"}, - {file = "tree_sitter-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ca72d841215b6573ed0655b3a5cd1133f9b69a6fa561aecad40dca9029d75b"}, - {file = "tree_sitter-0.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc0351cfe5022cec5a77645f647f92a936b38850346ed3f6d6babfbeeeca4d26"}, - {file = "tree_sitter-0.25.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1799609636c0193e16c38f366bda5af15b1ce476df79ddaae7dd274df9e44266"}, - {file = "tree_sitter-0.25.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e65ae456ad0d210ee71a89ee112ac7e72e6c2e5aac1b95846ecc7afa68a194c"}, - {file = "tree_sitter-0.25.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:49ee3c348caa459244ec437ccc7ff3831f35977d143f65311572b8ba0a5f265f"}, - {file = "tree_sitter-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:56ac6602c7d09c2c507c55e58dc7026b8988e0475bd0002f8a386cce5e8e8adc"}, - {file = "tree_sitter-0.25.2-cp311-cp311-win_arm64.whl", hash = "sha256:b3d11a3a3ac89bb8a2543d75597f905a9926f9c806f40fcca8242922d1cc6ad5"}, - {file = "tree_sitter-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ddabfff809ffc983fc9963455ba1cecc90295803e06e140a4c83e94c1fa3d960"}, - {file = "tree_sitter-0.25.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c0c0ab5f94938a23fe81928a21cc0fac44143133ccc4eb7eeb1b92f84748331c"}, - {file = "tree_sitter-0.25.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd12d80d91d4114ca097626eb82714618dcdfacd6a5e0955216c6485c350ef99"}, - {file = "tree_sitter-0.25.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b43a9e4c89d4d0839de27cd4d6902d33396de700e9ff4c5ab7631f277a85ead9"}, - {file = "tree_sitter-0.25.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb1706407c0e451c4f8cc016fec27d72d4b211fdd3173320b1ada7a6c74c3ac"}, - {file = "tree_sitter-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:6d0302550bbe4620a5dc7649517c4409d74ef18558276ce758419cf09e578897"}, - {file = "tree_sitter-0.25.2-cp312-cp312-win_arm64.whl", hash = "sha256:0c8b6682cac77e37cfe5cf7ec388844957f48b7bd8d6321d0ca2d852994e10d5"}, - {file = "tree_sitter-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0628671f0de69bb279558ef6b640bcfc97864fe0026d840f872728a86cd6b6cd"}, - {file = "tree_sitter-0.25.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ddcd3e291a749b62521f71fc953f66f5fd9743973fd6dd962b092773569601"}, - {file = "tree_sitter-0.25.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd88fbb0f6c3a0f28f0a68d72df88e9755cf5215bae146f5a1bdc8362b772053"}, - {file = "tree_sitter-0.25.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b878e296e63661c8e124177cc3084b041ba3f5936b43076d57c487822426f614"}, - {file = "tree_sitter-0.25.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d77605e0d353ba3fe5627e5490f0fbfe44141bafa4478d88ef7954a61a848dae"}, - {file = "tree_sitter-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:463c032bd02052d934daa5f45d183e0521ceb783c2548501cf034b0beba92c9b"}, - {file = "tree_sitter-0.25.2-cp313-cp313-win_arm64.whl", hash = "sha256:b3f63a1796886249bd22c559a5944d64d05d43f2be72961624278eff0dcc5cb8"}, - {file = "tree_sitter-0.25.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65d3c931013ea798b502782acab986bbf47ba2c452610ab0776cf4a8ef150fc0"}, - {file = "tree_sitter-0.25.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bda059af9d621918efb813b22fb06b3fe00c3e94079c6143fcb2c565eb44cb87"}, - {file = "tree_sitter-0.25.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eac4e8e4c7060c75f395feec46421eb61212cb73998dbe004b7384724f3682ab"}, - {file = "tree_sitter-0.25.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:260586381b23be33b6191a07cea3d44ecbd6c01aa4c6b027a0439145fcbc3358"}, - {file = "tree_sitter-0.25.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d2ee1acbacebe50ba0f85fff1bc05e65d877958f00880f49f9b2af38dce1af0"}, - {file = "tree_sitter-0.25.2-cp314-cp314-win_amd64.whl", hash = "sha256:4973b718fcadfb04e59e746abfbb0288694159c6aeecd2add59320c03368c721"}, - {file = "tree_sitter-0.25.2-cp314-cp314-win_arm64.whl", hash = "sha256:b8d4429954a3beb3e844e2872610d2a4800ba4eb42bb1990c6a4b1949b18459f"}, + {file = "tree_sitter-0.26.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff527388df14cb5009f9274faf78cc69a7393ae6acf3b04784b8acca249519c5"}, + {file = "tree_sitter-0.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7bcbadfa614326debef581957d5c780a9d7f66065c13deea61aa21d1dd36263f"}, + {file = "tree_sitter-0.26.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f941cea06128c1f74f8937a8e2a90c7db49cf4be6647cd9e07d92a306d91517"}, + {file = "tree_sitter-0.26.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e9e46b664887d8c1014f1fb33e09454bbdd9ec1fe29b7fd02dde7b46bc1bb81a"}, + {file = "tree_sitter-0.26.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:763627db05db34f12333081bd7422cc1c675893d373cc870b3e9249e200700e4"}, + {file = "tree_sitter-0.26.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:17a1c5cfd3a05d5c7c86bf4282b6ef8092c91dc0a98390499669c3fedb7d1814"}, + {file = "tree_sitter-0.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:f289be0225ba2ace8e87d6c9639b2bc9ff2b5271afb7c5d39282a4a00e248682"}, + {file = "tree_sitter-0.26.0-cp310-cp310-win_arm64.whl", hash = "sha256:526a165a2cb1d1f79e247d400f0e0acd8d49a817d6f312d543513af200b1f886"}, + {file = "tree_sitter-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1d6fe0e8fb4df77b5ee816228e2c4475a63d8cc1d4d3a7ffd7097b2b87fc3e95"}, + {file = "tree_sitter-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:514a9bf8993e5210e7970736aaf6020d1759b670e195ef17b1c48f586aa30736"}, + {file = "tree_sitter-0.26.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10f0d4eb94aa7242dcb7f554bcd24dd7ba1c114f00d58759ba08c7a46c8ec51a"}, + {file = "tree_sitter-0.26.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:335294ce0504fcefde5245dff596778ffaf820205b98ae0b549c72e48855f1d8"}, + {file = "tree_sitter-0.26.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9997ba61368c48ed54e715676afadf703947a1542464e39d047764fb3624b01"}, + {file = "tree_sitter-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c56581ad256c4195a21bfe449fed5d44a02fe83a4a7d6e70e6ec302c881191c7"}, + {file = "tree_sitter-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f8793fd18ad7eec276ed4b51c097b4bf2002b357259b66b0d75db1f3f41c754"}, + {file = "tree_sitter-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:dea4b4e27d49e9ec5b785d4f994da000e6726882fcc6ad05ec98478500c71aef"}, + {file = "tree_sitter-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6cb2bd20efb2544c19ac54486ab7cb8ec7b36f913bbe1ce95df84acb96743d9c"}, + {file = "tree_sitter-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:918d89529786873f0982a0f59c2a303cd065fbfd1b903d71a8e4e1584f67b42e"}, + {file = "tree_sitter-0.26.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30a88be89ff1f2755297f81e8080d88b795dd98720c3f9fa2acf93873182cc95"}, + {file = "tree_sitter-0.26.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a6b333b0282d8bb0af741f9b018bd2523d4eecb2686bf6717066a625fecfaa4"}, + {file = "tree_sitter-0.26.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3f3c44339dd34fe8eb2b8d5aa7610660499a795f70376b130bbee7a437337280"}, + {file = "tree_sitter-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94550e13b6ae576969da40246f4c4abb206380b5375ad43f26dd9151d55438e3"}, + {file = "tree_sitter-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca89e361a276dbc934b28a43dd881199e25d34ff5493ee0ce45f3c52a6124a37"}, + {file = "tree_sitter-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:bc6cb01d5ee75c85424aa1f1c72a82d8f07fd52539a0f3c4a6ed3e8721079b84"}, + {file = "tree_sitter-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed0889dbed843ce45ede9f5169c0b2dea2222f12685844a03fadb81f12705867"}, + {file = "tree_sitter-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6189c6c340c7384357711e3d92645e96bfb79f7a502f86de1ebdb23eb43f7dab"}, + {file = "tree_sitter-0.26.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ff2e0750b7daa722302838356d7b65e303829b7eb73c915df127ddba115e1d1"}, + {file = "tree_sitter-0.26.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7075ef857ef86f327dbb72d1e2574dda78db5754b3a1fca6506acd7fe5d561a7"}, + {file = "tree_sitter-0.26.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:26c996c1edfee86e977bb3f5462e74fcec0d0b0db1e85a3c475875763caa03be"}, + {file = "tree_sitter-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00289bfe7978f3e0dc0ce69813a20fa9f44ea4c100b3ec62043e5eb74ccfc3a2"}, + {file = "tree_sitter-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:93e220cab7e6a823efeb2046c49171427de92ef71c7c681c01820d14d8d3721f"}, + {file = "tree_sitter-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:b31a8195d2f224224c530ac814632d98c1dcc123d227442c07c736e86b70d564"}, + {file = "tree_sitter-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5a3c93a352b7e6f70f73e121bbfa2d0117ba7478bd51114ed35c91b0b78814fa"}, + {file = "tree_sitter-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5fc2f41bf246ff2f70a9cc3690be35ec7580a4923151873d898c8bcb1a4503d3"}, + {file = "tree_sitter-0.26.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8ea92a255c91671a7ec4625aba3ab7bb5220c423630ffbf83c45d7312abe084"}, + {file = "tree_sitter-0.26.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f665510f0fcf4636fb9696f1f7853bed7a3bd764b7bb0cb8494e619c14ed5a0c"}, + {file = "tree_sitter-0.26.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:253df7ab82cc0a9d311cd65f06e9f99fb3eac55996ae9fc94da22f123a861b90"}, + {file = "tree_sitter-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ff80d4833d330a73184a3ac5132abe93c575d2dea31975c6f15c0d21fef238aa"}, + {file = "tree_sitter-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:a4033fecc8f606c7f2e8b8014d0057b74668a7f0152763606f7bc25c5f9ec64c"}, + {file = "tree_sitter-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:823251c4b6725a7c03ed497a339135ede7ae4bdde75bb8be7ef5e305aeb4ff52"}, + {file = "tree_sitter-0.26.0.tar.gz", hash = "sha256:b40c219edccc4564530c96f8f1556f6202b37cda964d1cbd7bd2b7e68b40a245"}, ] [package.extras] -docs = ["sphinx (>=8.1,<9.0)", "sphinx-book-theme"] -tests = ["tree-sitter-html (>=0.23.2)", "tree-sitter-javascript (>=0.23.1)", "tree-sitter-json (>=0.24.8)", "tree-sitter-python (>=0.23.6)", "tree-sitter-rust (>=0.23.2)"] +docs = ["sphinx (>=8.2,<9.0)", "sphinx-book-theme"] +tests = ["tree-sitter-html (==0.23.2)", "tree-sitter-javascript (==0.25.0)", "tree-sitter-json (==0.24.8)", "tree-sitter-python (==0.25.0)", "tree-sitter-rust (==0.24.2)"] [[package]] name = "tree-sitter-go" @@ -4219,19 +4103,20 @@ core = ["tree-sitter (>=0.24,<1.0)"] [[package]] name = "tree-sitter-rust" -version = "0.24.0" +version = "0.24.2" description = "Rust grammar for tree-sitter" optional = false python-versions = ">=3.9" files = [ - {file = "tree_sitter_rust-0.24.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7ea455443f5ab245afd8c5ce63a8ae38da455ef27437b459ce3618a9d4ec4f9a"}, - {file = "tree_sitter_rust-0.24.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:a0a1a2694117a0e86e156b28ee7def810ec94e52402069bf805be22d43e3c1a1"}, - {file = "tree_sitter_rust-0.24.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3362992ea3150b0dd15577dd59caef4f2926b6e10806f2bb4f2533485acee2f"}, - {file = "tree_sitter_rust-0.24.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2c1f4b87df568352a9e523600af7cb32c5748dc75275f4794d6f811ab13dfe"}, - {file = "tree_sitter_rust-0.24.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:615f989241b717f14105b1bc621ff0c2200c86f1c3b36f1842d61f6605021152"}, - {file = "tree_sitter_rust-0.24.0-cp39-abi3-win_amd64.whl", hash = "sha256:2e29be0292eaf1f99389b3af4281f92187612af31ba129e90f4755f762993441"}, - {file = "tree_sitter_rust-0.24.0-cp39-abi3-win_arm64.whl", hash = "sha256:7a0538eaf4063b443c6cd80a47df19249f65e27dbdf129396a9193749912d0c0"}, - {file = "tree_sitter_rust-0.24.0.tar.gz", hash = "sha256:c7185f482717bd41f24ffcd90b5ee24e7e0d6334fecce69f1579609994cd599d"}, + {file = "tree_sitter_rust-0.24.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3620cfd12340efa43082d45df76349ff511893a9c361da2f8d6d51e307020a59"}, + {file = "tree_sitter_rust-0.24.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:01a46622735498493f29f3e628a90de95c96a07bfbeb88996243eb986b1cee36"}, + {file = "tree_sitter_rust-0.24.2-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e033c5a93b57c88e0a835880de39fc802909ff69f57aaff6000211c196ea5190"}, + {file = "tree_sitter_rust-0.24.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d76d1208c3638b871236090759dfc13d478921320653a6c9da5336e7c58f65a"}, + {file = "tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87930163a462408c49ab62c667e74029bc26b4cc7123dd1bdc7352215786c64a"}, + {file = "tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:da2b86099028fd42c6cd32878b7b16b01f8aac0f7b0e98742b7fa6bc3cf09b89"}, + {file = "tree_sitter_rust-0.24.2-cp39-abi3-win_amd64.whl", hash = "sha256:4529c125d928882ddfb879fdc6bc0704913261ecc078b6fa7902559e0daf200d"}, + {file = "tree_sitter_rust-0.24.2-cp39-abi3-win_arm64.whl", hash = "sha256:66ba90f61bd54f4c4f5d30434957daf64507c16b0313df76becb37d63f70a227"}, + {file = "tree_sitter_rust-0.24.2.tar.gz", hash = "sha256:54fb02a5911e345308b405174465112479f56dc39e3f1e7744d7568595f00db9"}, ] [package.extras] @@ -4239,19 +4124,20 @@ core = ["tree-sitter (>=0.22,<1.0)"] [[package]] name = "tree-sitter-swift" -version = "0.0.1" +version = "0.7.3" description = "Swift grammar for tree-sitter" optional = false python-versions = ">=3.8" files = [ - {file = "tree_sitter_swift-0.0.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:45d8d61c3dc9b72ec721ed93e5920d24f87bd6350298bc7c4edc261066cda745"}, - {file = "tree_sitter_swift-0.0.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c669e830cfeec55d326c79478e25465e1b61207fdb9d5459533fd5f735051d20"}, - {file = "tree_sitter_swift-0.0.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3caac02783238e9c72349bc4a8db11be258666d5ae1856ac54c46ca6086ecfc9"}, - {file = "tree_sitter_swift-0.0.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d009e46d9800cf5ca3c585163d04c70660d1383f350dad93d08bf00735d2714b"}, - {file = "tree_sitter_swift-0.0.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f61776dbf4bcb3fd9a852c69a3f61d08abaad4dd22272bf04272f7ed26aa6d75"}, - {file = "tree_sitter_swift-0.0.1-cp38-abi3-win_amd64.whl", hash = "sha256:4ca542091e411b2236c862a38e1c1b34323c909f011c122c10747e6010e13693"}, - {file = "tree_sitter_swift-0.0.1-cp38-abi3-win_arm64.whl", hash = "sha256:ce3add5c7156e2e5329e675e7ac6a3389cb1e9ebff18c9248538e54fc738390d"}, - {file = "tree_sitter_swift-0.0.1.tar.gz", hash = "sha256:d43b0baf413ba4b049f92eadc074805cb6403655c905d3af24f12fce1c9f561b"}, + {file = "tree_sitter_swift-0.7.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2531ec866c22ea52384e2786e07f3b2bb396c6446428a2df02cc74af3f7e6b6a"}, + {file = "tree_sitter_swift-0.7.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ee627e027d0868c552beca13dcdfa9944662b126f642464c5038ee3204e68340"}, + {file = "tree_sitter_swift-0.7.3-cp38-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f38feeb4f7350c8b30d567a0dc08bf1eeaa67c241b6888d72a45a8b1a4aa7187"}, + {file = "tree_sitter_swift-0.7.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eee02fecb60a07267edd123148c583d6ec9efc5d7fcb25e53da4e56869fd4cf3"}, + {file = "tree_sitter_swift-0.7.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f30c30831f090ebe245f54ddcd280d2c5f7020ba17d6bbec1662bbfae140c467"}, + {file = "tree_sitter_swift-0.7.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01c1e812289a2f7f01f63627a5d94a0b57d69332e8b52624becfe79ee8061651"}, + {file = "tree_sitter_swift-0.7.3-cp38-abi3-win_amd64.whl", hash = "sha256:4b1de6122cbd82b2cea6d3a295f9f5f9297601b829061119e161da17a7ba7d17"}, + {file = "tree_sitter_swift-0.7.3-cp38-abi3-win_arm64.whl", hash = "sha256:af44acc50d16f284abb607ae0cf7f81011d5566283d6c62a045a549a9331a653"}, + {file = "tree_sitter_swift-0.7.3.tar.gz", hash = "sha256:a87f1dba3050a346ee3442aad8d727afd74555dea258e31c71c7934d8c04af9b"}, ] [package.extras] @@ -4279,73 +4165,55 @@ core = ["tree-sitter (>=0.23,<1.0)"] [[package]] name = "typer" -version = "0.21.1" +version = "0.27.1" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01"}, - {file = "typer-0.21.1.tar.gz", hash = "sha256:ea835607cd752343b6b2b7ce676893e5a0324082268b48f27aa058bdb7d2145d"}, + {file = "typer-0.27.1-py3-none-any.whl", hash = "sha256:53150287edd11baeb4e4722c8e394fcdf8181c0ae89485cba8d25c778d5edd56"}, + {file = "typer-0.27.1.tar.gz", hash = "sha256:a79bef8469a79c45498e7b814ecf8d603cc7644e9acbd9e19cac0334240b18df"}, ] [package.dependencies] -click = ">=8.0.0" -rich = ">=10.11.0" +annotated-doc = ">=0.0.2" +colorama = {version = "*", markers = "platform_system == \"Windows\""} +rich = ">=13.8.0" shellingham = ">=1.3.0" -typing-extensions = ">=3.7.4.3" - -[[package]] -name = "typer-slim" -version = "0.21.1" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.9" -files = [ - {file = "typer_slim-0.21.1-py3-none-any.whl", hash = "sha256:6e6c31047f171ac93cc5a973c9e617dbc5ab2bddc4d0a3135dc161b4e2020e0d"}, - {file = "typer_slim-0.21.1.tar.gz", hash = "sha256:73495dd08c2d0940d611c5a8c04e91c2a0a98600cbd4ee19192255a233b6dbfd"}, -] - -[package.dependencies] -click = ">=8.0.0" -typing-extensions = ">=3.7.4.3" - -[package.extras] -standard = ["rich (>=10.11.0)", "shellingham (>=1.3.0)"] [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, + {file = "typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8"}, + {file = "typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5"}, ] [[package]] name = "typing-inspection" -version = "0.4.2" +version = "0.4.4" description = "Runtime typing introspection tools" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, - {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, + {file = "typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147"}, + {file = "typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47"}, ] [package.dependencies] -typing-extensions = ">=4.12.0" +typing-extensions = ">=4.15.0" [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, - {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, + {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, + {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, ] [package.extras] @@ -4356,13 +4224,13 @@ zstd = ["backports-zstd (>=1.0.0)"] [[package]] name = "uvicorn" -version = "0.40.0" +version = "0.52.3" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.10" files = [ - {file = "uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee"}, - {file = "uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea"}, + {file = "uvicorn-0.52.3-py3-none-any.whl", hash = "sha256:116af2710dbf47c80f463cd20ee4884b6662f4c9f227d797ddc7279d2fcc2c7c"}, + {file = "uvicorn-0.52.3.tar.gz", hash = "sha256:18857b9e6579300be55c91c0a1cfd37d9a2cf0cabea33b88275f199eb73b8b58"}, ] [package.dependencies] @@ -4370,294 +4238,119 @@ click = ">=7.0" h11 = ">=0.8" [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] - -[[package]] -name = "xxhash" -version = "3.6.0" -description = "Python binding for xxHash" -optional = false -python-versions = ">=3.7" -files = [ - {file = "xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71"}, - {file = "xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:89952ea539566b9fed2bbd94e589672794b4286f342254fad28b149f9615fef8"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e6f2ffb07a50b52465a1032c3cf1f4a5683f944acaca8a134a2f23674c2058"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b5b848ad6c16d308c3ac7ad4ba6bede80ed5df2ba8ed382f8932df63158dd4b2"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a034590a727b44dd8ac5914236a7b8504144447a9682586c3327e935f33ec8cc"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a8f1972e75ebdd161d7896743122834fe87378160c20e97f8b09166213bf8cc"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee34327b187f002a596d7b167ebc59a1b729e963ce645964bbc050d2f1b73d07"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:339f518c3c7a850dd033ab416ea25a692759dc7478a71131fe8869010d2b75e4"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf48889c9630542d4709192578aebbd836177c9f7a4a2778a7d6340107c65f06"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5576b002a56207f640636056b4160a378fe36a58db73ae5c27a7ec8db35f71d4"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af1f3278bd02814d6dedc5dec397993b549d6f16c19379721e5a1d31e132c49b"}, - {file = "xxhash-3.6.0-cp310-cp310-win32.whl", hash = "sha256:aed058764db109dc9052720da65fafe84873b05eb8b07e5e653597951af57c3b"}, - {file = "xxhash-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:e82da5670f2d0d98950317f82a0e4a0197150ff19a6df2ba40399c2a3b9ae5fb"}, - {file = "xxhash-3.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a082ffff8c6ac07707fb6b671caf7c6e020c75226c561830b73d862060f281d"}, - {file = "xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a"}, - {file = "xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3"}, - {file = "xxhash-3.6.0-cp311-cp311-win32.whl", hash = "sha256:d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd"}, - {file = "xxhash-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef"}, - {file = "xxhash-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7"}, - {file = "xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c"}, - {file = "xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae"}, - {file = "xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb"}, - {file = "xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c"}, - {file = "xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829"}, - {file = "xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec"}, - {file = "xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd"}, - {file = "xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799"}, - {file = "xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392"}, - {file = "xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6"}, - {file = "xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702"}, - {file = "xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033"}, - {file = "xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec"}, - {file = "xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8"}, - {file = "xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746"}, - {file = "xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e"}, - {file = "xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5"}, - {file = "xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f"}, - {file = "xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad"}, - {file = "xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679"}, - {file = "xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4"}, - {file = "xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518"}, - {file = "xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119"}, - {file = "xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f"}, - {file = "xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95"}, - {file = "xxhash-3.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7dac94fad14a3d1c92affb661021e1d5cbcf3876be5f5b4d90730775ccb7ac41"}, - {file = "xxhash-3.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6965e0e90f1f0e6cb78da568c13d4a348eeb7f40acfd6d43690a666a459458b8"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2ab89a6b80f22214b43d98693c30da66af910c04f9858dd39c8e570749593d7e"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4903530e866b7a9c1eadfd3fa2fbe1b97d3aed4739a80abf506eb9318561c850"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4da8168ae52c01ac64c511d6f4a709479da8b7a4a1d7621ed51652f93747dffa"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97460eec202017f719e839a0d3551fbc0b2fcc9c6c6ffaa5af85bbd5de432788"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45aae0c9df92e7fa46fbb738737324a563c727990755ec1965a6a339ea10a1df"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d50101e57aad86f4344ca9b32d091a2135a9d0a4396f19133426c88025b09f1"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9085e798c163ce310d91f8aa6b325dda3c2944c93c6ce1edb314030d4167cc65"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:a87f271a33fad0e5bf3be282be55d78df3a45ae457950deb5241998790326f87"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:9e040d3e762f84500961791fa3709ffa4784d4dcd7690afc655c095e02fff05f"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b0359391c3dad6de872fefb0cf5b69d55b0655c55ee78b1bb7a568979b2ce96b"}, - {file = "xxhash-3.6.0-cp38-cp38-win32.whl", hash = "sha256:e4ff728a2894e7f436b9e94c667b0f426b9c74b71f900cf37d5468c6b5da0536"}, - {file = "xxhash-3.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:01be0c5b500c5362871fc9cfdf58c69b3e5c4f531a82229ddb9eb1eb14138004"}, - {file = "xxhash-3.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc604dc06027dbeb8281aeac5899c35fcfe7c77b25212833709f0bff4ce74d2a"}, - {file = "xxhash-3.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:277175a73900ad43a8caeb8b99b9604f21fe8d7c842f2f9061a364a7e220ddb7"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cfbc5b91397c8c2972fdac13fb3e4ed2f7f8ccac85cd2c644887557780a9b6e2"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2762bfff264c4e73c0e507274b40634ff465e025f0eaf050897e88ec8367575d"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f171a900d59d51511209f7476933c34a0c2c711078d3c80e74e0fe4f38680ec"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:780b90c313348f030b811efc37b0fa1431163cb8db8064cf88a7936b6ce5f222"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b242455eccdfcd1fa4134c431a30737d2b4f045770f8fe84356b3469d4b919"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a75ffc1bd5def584129774c158e108e5d768e10b75813f2b32650bb041066ed6"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1fc1ed882d1e8df932a66e2999429ba6cc4d5172914c904ab193381fba825360"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:44e342e8cc11b4e79dae5c57f2fb6360c3c20cc57d32049af8f567f5b4bcb5f4"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c2f9ccd5c4be370939a2e17602fbc49995299203da72a3429db013d44d590e86"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02ea4cb627c76f48cd9fb37cf7ab22bd51e57e1b519807234b473faebe526796"}, - {file = "xxhash-3.6.0-cp39-cp39-win32.whl", hash = "sha256:6551880383f0e6971dc23e512c9ccc986147ce7bfa1cd2e4b520b876c53e9f3d"}, - {file = "xxhash-3.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:7c35c4cdc65f2a29f34425c446f2f5cdcd0e3c34158931e1cc927ece925ab802"}, - {file = "xxhash-3.6.0-cp39-cp39-win_arm64.whl", hash = "sha256:ffc578717a347baf25be8397cb10d2528802d24f94cfc005c0e44fef44b5cdd6"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d"}, - {file = "xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6"}, -] +standard = ["httptools (>=0.8.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1)", "watchfiles (>=0.20)", "websockets (>=13.0)"] [[package]] name = "yarl" -version = "1.22.0" +version = "1.24.5" description = "Yet another URL library" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e"}, - {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f"}, - {file = "yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf"}, - {file = "yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a"}, - {file = "yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c"}, - {file = "yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147"}, - {file = "yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb"}, - {file = "yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6"}, - {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0"}, - {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda"}, - {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc"}, - {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737"}, - {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467"}, - {file = "yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea"}, - {file = "yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca"}, - {file = "yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b"}, - {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511"}, - {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6"}, - {file = "yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028"}, - {file = "yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d"}, - {file = "yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503"}, - {file = "yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65"}, - {file = "yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e"}, - {file = "yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d"}, - {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7"}, - {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967"}, - {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed"}, - {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6"}, - {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e"}, - {file = "yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca"}, - {file = "yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b"}, - {file = "yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376"}, - {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f"}, - {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2"}, - {file = "yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74"}, - {file = "yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df"}, - {file = "yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb"}, - {file = "yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2"}, - {file = "yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82"}, - {file = "yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a"}, - {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124"}, - {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa"}, - {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7"}, - {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d"}, - {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520"}, - {file = "yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8"}, - {file = "yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c"}, - {file = "yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74"}, - {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53"}, - {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a"}, - {file = "yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c"}, - {file = "yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601"}, - {file = "yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a"}, - {file = "yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df"}, - {file = "yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2"}, - {file = "yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b"}, - {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273"}, - {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a"}, - {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d"}, - {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02"}, - {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67"}, - {file = "yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95"}, - {file = "yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d"}, - {file = "yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b"}, - {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10"}, - {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3"}, - {file = "yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9"}, - {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f"}, - {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0"}, - {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e"}, - {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708"}, - {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f"}, - {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d"}, - {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8"}, - {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5"}, - {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f"}, - {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62"}, - {file = "yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03"}, - {file = "yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249"}, - {file = "yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b"}, - {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4"}, - {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683"}, - {file = "yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b"}, - {file = "yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e"}, - {file = "yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590"}, - {file = "yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2"}, - {file = "yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da"}, - {file = "yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784"}, - {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b"}, - {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694"}, - {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d"}, - {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd"}, - {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da"}, - {file = "yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2"}, - {file = "yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79"}, - {file = "yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33"}, - {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1"}, - {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca"}, - {file = "yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53"}, - {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c"}, - {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf"}, - {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face"}, - {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b"}, - {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486"}, - {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138"}, - {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a"}, - {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529"}, - {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093"}, - {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c"}, - {file = "yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e"}, - {file = "yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27"}, - {file = "yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1"}, - {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748"}, - {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859"}, - {file = "yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9"}, - {file = "yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054"}, - {file = "yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b"}, - {file = "yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60"}, - {file = "yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890"}, - {file = "yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba"}, - {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca"}, - {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba"}, - {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b"}, - {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e"}, - {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8"}, - {file = "yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b"}, - {file = "yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed"}, - {file = "yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2"}, - {file = "yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff"}, - {file = "yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71"}, + {file = "yarl-1.24.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88f50c94e21a0a7f14042c015b0eba1881af78562e7bf007e0033e624da59750"}, + {file = "yarl-1.24.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6efbccc3d7f75d5b03105172a8dc86d82ba4da86817952529dd93185f4a88be2"}, + {file = "yarl-1.24.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0ebfaffe1a16cb72141c8e09f18cc76856dbe58639f393a4f2b26e474b96b871"}, + {file = "yarl-1.24.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ac73abdc7ab75610f95a8fd994c6457e87752b02a63987e188f937a1fc180f0"}, + {file = "yarl-1.24.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4d97a951a81039050e45f04e96689b58b8243fa5e62aa14fe67cb6075300885e"}, + {file = "yarl-1.24.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fe7b7bb170daccbba19ad33012d2b15f1e7942296fd4d45fc1b79013da8cc0f2"}, + {file = "yarl-1.24.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89a1bbb58e0e3f7a283653d854b1e95d65e5cfd4af224dac5f02629ec1a3e621"}, + {file = "yarl-1.24.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fa5e51397466ea7e98de493fa2ff1b8193cfef8a7b0f9b4842f92d342df0dba"}, + {file = "yarl-1.24.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4103b77b8a8225e413107d2349b65eb3c1c52627b5cc5c3c4c1c6a798b218950"}, + {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f9f3e9c8a9ecffa57bef8fb4fa19e5fa4d2d8307cf6bac5b1fca5e5860f4ba00"}, + {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0ebc836c47a6477e182169c6a476fc691d12b518894bf7dd2572f0d59f1c7ed"}, + {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:96d30286dd02679e32a39aa8f0b7498fc847fcda46cfc09df5513e82ce252440"}, + {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:fd8c81f346b58f45818d09ea11db69a8d5fd34a224b79871f6d44f12cd7977b1"}, + {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5c55256dee8f4b27bfbf636c8363383c7c8db7890c7cba5217d7bd5f5f21dab6"}, + {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f4d8cf085a4c6a40fb97ea0f46938a8df43c85d31f9d45e2a8867ea9293790d"}, + {file = "yarl-1.24.5-cp310-cp310-win_amd64.whl", hash = "sha256:240cbec09667c1fed4c6cd0060b9ec57332427d7441289a2ed8875dc9fb2b224"}, + {file = "yarl-1.24.5-cp310-cp310-win_arm64.whl", hash = "sha256:8a6987eaad834cb32dd57d9d582225f0054a5d1af706ccfbbdba735af4927e13"}, + {file = "yarl-1.24.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2c1fe720934a16ea8e7146175cba2126f87f54912c8c5435e7f7c7a51ef808d3"}, + {file = "yarl-1.24.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c687ed078e145f5fd53a14854beff320e1d2ab76df03e2009c98f39a0f68f39a"}, + {file = "yarl-1.24.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:709f1efed56c4a145793c046cd4939f9959bcd818979a787b77d8e09c57a0840"}, + {file = "yarl-1.24.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874019bd513008b009f58657134e5d0c5e030b3559bd0553976837adf52fe966"}, + {file = "yarl-1.24.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a4582acf7ef76482f6f511ebaf1946dae7f2e85ec4728b81a678c01df63bd723"}, + {file = "yarl-1.24.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2cabe6546e41dabe439999a23fcb5246e0c3b595b4315b96ef755252be90caeb"}, + {file = "yarl-1.24.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:17f57620f5475b3c69109376cc87e42a7af5db13c9398e4292772a706ff10780"}, + {file = "yarl-1.24.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:570fec8fbd22b032733625f03f10b7ff023bc399213db15e72a7acaef28c2f4e"}, + {file = "yarl-1.24.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5fede79c6f73ff2c3ef822864cb1ada23196e62756df53bc6231d351a49516a2"}, + {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ccf9aca873b767977c73df497a85dbedee4ee086ae9ae49dc461333b9b79f58"}, + {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ad5d8201d310b031e6cd839d9bac2d4e5a01533ce5d3d5b50b7de1ef3af1de61"}, + {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:841f0852f48fefea3b12c9dfec00704dfa3aef5215d0e3ce564bb3d7cd8d57c6"}, + {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:9baafc71b04f8f4bb0703b21d6fc9f0c30b346c636a532ff16ec8491a5ea4b1f"}, + {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d897129df1a22b12aeed2c2c98df0785a2e8e6e0bde87b389491d0025c187077"}, + {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd625535328fd9882374356269227670189adfcc6a2d90284f323c05862eecbd"}, + {file = "yarl-1.24.5-cp311-cp311-win_amd64.whl", hash = "sha256:f4239bbec5a3577ddb49e4b50aeb32d8e5792098262ae2f63723f916a29b1a25"}, + {file = "yarl-1.24.5-cp311-cp311-win_arm64.whl", hash = "sha256:3ac6aff147deb9c09461b2d4bbdf6256831198f5d8a23f5d37138213090b6d8a"}, + {file = "yarl-1.24.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d693396e5aea78db03decd60aec9ece16c9b40ba00a587f089615ff4e718a81d"}, + {file = "yarl-1.24.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3363fcc96e665878946ad7a106b9a13eac0541766a690ef287c0232ac768b6ec"}, + {file = "yarl-1.24.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9d399bdcfb4a0f659b9b3788bbc89babe63d9a6a65aacdf4d4e7065ff2e6316c"}, + {file = "yarl-1.24.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90333fd89b43c0d08ac85f3f1447593fc2c66de18c3d6378d7125ea118dc7a54"}, + {file = "yarl-1.24.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:665b0a2c463cc9423dd647e0bfd9f4ccc9b50f768c55304d5e9f80b177c1de12"}, + {file = "yarl-1.24.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e006d3a974c4ee19512e5f058abedb6eef36a5e553c14812bdeba1758d812e6d"}, + {file = "yarl-1.24.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7d42c531243450ef0d4d9c172e7ed6ef052640f195629065041b5add4e058d1"}, + {file = "yarl-1.24.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f08c7513ecef5aad65687bfdf6bc601ae9fccd04a42904501f8f7141abad9eb9"}, + {file = "yarl-1.24.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c95b17fe34ed802f17e205112e6e10db92275c34fee290aa9bdc55a9c724027"}, + {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56b149b22de33b23b0c6077ab9518c6dcb538ad462e1830e68d06591ccf6e38b"}, + {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a8fe66b8f300da93798025a785a5b90b42f3810dc2b72283ff84a41aaaebc293"}, + {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:377fe3732edbaf78ee74efdf2c9f49f6e99f20e7f9d2649fda3eb4badd77d76e"}, + {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e8ffa78582120024f476a611d7befc123cee59e47e8309d470cf667d806e613b"}, + {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:daba5e594f06114e37db186efd2dd916609071e59daca901a0a2e71f02b142ce"}, + {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:65be18ec59496c13908f02a2472751d9ef840b4f3fb5726f129306bf6a2a7bba"}, + {file = "yarl-1.24.5-cp312-cp312-win_amd64.whl", hash = "sha256:a929d878fec099030c292803b31e5d5540a7b6a31e6a3cc76cb4685fc2a2f51b"}, + {file = "yarl-1.24.5-cp312-cp312-win_arm64.whl", hash = "sha256:7ce27823052e2013b597e0c738b13e7e36b8ccb9400df8959417b052ab0fd92c"}, + {file = "yarl-1.24.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:79af890482fc94648e8cde4c68620378f7fef60932710fa17a66abc039244da2"}, + {file = "yarl-1.24.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46c2f213e23a04b93a392942d782eb9e413e6ef6bf7c8c53884e599a5c174dcb"}, + {file = "yarl-1.24.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92ab3e11448f2ff7bf53c5a26eff0edc086898ec8b21fb154b85839ce1d88075"}, + {file = "yarl-1.24.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebb0ec7f17803063d5aeb982f3b1bd2b2f4e4fae6751226cbd6ba1fcfe9e63ff"}, + {file = "yarl-1.24.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:82632daed195dcc8ea664e8556dc9bdbd671960fb3776bd92806ce05792c2448"}, + {file = "yarl-1.24.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e549287ef628fecba270045c9701b0c564563a9b0577d24a4ec75b8ab8040f"}, + {file = "yarl-1.24.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fcd3b77e2f17bbe4ca56ec7bcb07992647d19d0b9c05d84886dcd6f9eb810afd"}, + {file = "yarl-1.24.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d46b86567dd4e248c6c159fcbcdcce01e0a5c8a7cd2334a0fff759d0fa075b16"}, + {file = "yarl-1.24.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f72c74aa99359e27a2ee8d6613fefa28b5f76a983c083074dfc2aaa4ab46213"}, + {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f45789ce415a7ec0820dc4f82925f9b5f7732070be1dec1f5f23ec381435a24"}, + {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6e73e7fe93f17a7b191f52ec9da9dd8c06a8fe735a1ecbd13b97d1c723bff385"}, + {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4a36f9becdd4c5c52a20c3e9484128b070b1dcfc8944c006f3a528295a359a9c"}, + {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:7bcbe0fcf850eae67b6b01749815a4f7161c560a844c769ad7b48fcd99f791c4"}, + {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:24e861e9630e0daddcb9191fb187f60f034e17a4426f8101279f0c475cd74144"}, + {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9335a099ad87287c37fe5d1a982ff392fa5efe5d14b40a730b1ec1d6a41382b4"}, + {file = "yarl-1.24.5-cp313-cp313-win_amd64.whl", hash = "sha256:2dbe06fc16bc91502bca713704022182e5729861ae00277c3a23354b40929740"}, + {file = "yarl-1.24.5-cp313-cp313-win_arm64.whl", hash = "sha256:6b8536851f9f65e7f00c7a1d49ba7f2be0ffe2c11555367fc9f50d9f842410a1"}, + {file = "yarl-1.24.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2729fcfc4f6a596fb0c50f32090400aa9367774ac296a00387e65098c0befa76"}, + {file = "yarl-1.24.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ff330d3c30db4eb6b01d79e29d2d0b407a7ecad39cfd9ec993ece57396a2ec0d"}, + {file = "yarl-1.24.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e42d75862735da90e7fc5a7b23db0c976f737113a54b3c9777a9b665e9cbff75"}, + {file = "yarl-1.24.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3732e66413163e72508da9eff9ce9d2846fde51fae45d3605393d3e6cd303e9"}, + {file = "yarl-1.24.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5b8ee53be440a0cffc991a27be3057e0530122548dbe7c0892df08822fce5ede"}, + {file = "yarl-1.24.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:af3aefa655adb5869491fa907e652290386800ae99cc50095cba71e2c6aefdca"}, + {file = "yarl-1.24.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2120b96872df4a117cde97d270bac96aea7cc52205d305cf4611df694a487027"}, + {file = "yarl-1.24.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66410eb6345d467151934b49bfa70fb32f5b35a6140baa40ad97d6436abea2e9"}, + {file = "yarl-1.24.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4af7b7e1be0a69bee8210735fe6dcfc38879adfac6d62e789d53ba432d1ffa41"}, + {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa139875ff98ab97da323cfadfaff08900d1ad42f1b5087b0b812a55c5a06373"}, + {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:0055afc45e864b92729ac7600e2d102c17bef060647e74bca75fa84d66b9ff36"}, + {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f0e466ed7511fe9d459a819edbc6c2585c0b6eabde9fa8a8947552468a7a6ef0"}, + {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f141474e85b7e54998ec5180530a7cda99ab29e282fa50e0756d89981a9b43c5"}, + {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e2935f8c39e3b03e83519292d78f075189978f3f4adc15a78144c7c8e2a1cba5"}, + {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9d1216a7f6f77836617dba35687c5b78a4170afc3c3f18fc788f785ba26565c4"}, + {file = "yarl-1.24.5-cp314-cp314-win_amd64.whl", hash = "sha256:5ba4f78df2bcc19f764a4b26a8a4f5049c110090ad5825993aacb052bf8003ad"}, + {file = "yarl-1.24.5-cp314-cp314-win_arm64.whl", hash = "sha256:9e4e16c73d717c5cf27626c524d0a2e261ad20e46932b2670f64ad5dde23e26f"}, + {file = "yarl-1.24.5-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e1ae548a9d901adca07899a4147a7c826bbcc06239d3ce9a59f57886a28a4c88"}, + {file = "yarl-1.24.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff405d91509d88e8d44129cd87b18d70acd1f0c1aeabd7bc3c46792b1fe2acba"}, + {file = "yarl-1.24.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:47e98aab9d8d82ff682e7b0b5dded33bf138a32b817fcf7fa3b27b2d7c412928"}, + {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0a658a6d3fafee5c6f63c58f3e785c8c43c93fbc02bf9f2b6663f8185e0971f"}, + {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4377407001ca3c057773f44d8ddd6358fa5f691407c1ba92210bd3cf8d9e4c95"}, + {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7c0494a31a1ac5461a226e7947a9c9b78c44e1dc7185164fa7e9651557a5d9bc"}, + {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a7cff474ab7cd149765bb784cf6d78b32e18e20473fb7bda860bce98ab58e9da"}, + {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbb833ccacdb5519eff9b8b71ee618cc2801c878e77e288775d77c3a2ced858a"}, + {file = "yarl-1.24.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:82f75e05912e84b7a0fe57075d9c59de3cb352b928330f2eb69b2e1f54c3e1f0"}, + {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:16a2f5010280020e90f5330257e6944bc33e73593b136cc5a241e6c1dc292498"}, + {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ffcd54362564dc1a30fb74d8b8a6e5a6b11ebd5e27266adc3b7427a21a6c9104"}, + {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0465ec8cedc2349b97a6b595ace64084a50c6e839eca40aa0626f38b8350e331"}, + {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4db9aecb141cb7a5447171b57aa1ed3a8fee06af40b992ffc31206c0b0121550"}, + {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f540c013589084679a6c7fac07096b10159737918174f5dfc5e11bf5bca4dfe6"}, + {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a61834fb15d81322d872eaafd333838ae7c9cea84067f232656f75965933d047"}, + {file = "yarl-1.24.5-cp314-cp314t-win_amd64.whl", hash = "sha256:5c88e5815a49d289e599f3513aa7fde0bc2092ff188f99c940f007f90f53d104"}, + {file = "yarl-1.24.5-cp314-cp314t-win_arm64.whl", hash = "sha256:cf139c02f5f23ef6532040a30ff662c00a318c952334f211046b8e60b7f17688"}, + {file = "yarl-1.24.5-py3-none-any.whl", hash = "sha256:a33700d13d9b7d84fd10947b09ff69fb9a792e519c8cb9764a3ca70baa6c23a7"}, + {file = "yarl-1.24.5.tar.gz", hash = "sha256:e81b83143bee16329c23db3c1b2d82b29892fcbcb849186d2f6e98a5abe9a57f"}, ] [package.dependencies] @@ -4667,24 +4360,24 @@ propcache = ">=0.2.1" [[package]] name = "zipp" -version = "3.23.0" +version = "4.1.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, - {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, + {file = "zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f"}, + {file = "zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.14)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] +enabler = ["pytest-enabler (>=3.4)"] test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] +type = ["pytest-mypy (>=1.0.1)"] [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.14" -content-hash = "9a3b7297f4254b101ea7184fad127421f243734e6923014c393615f87f7f7db6" +content-hash = "badd01a46903eaea692f8296a6439cc15e82b881c296135f06605f1b07916459" diff --git a/pyproject.toml b/pyproject.toml index b802600..c34506b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "codespy-ai" -version = "0.4.1" +version = "1.0.0" description = "Code review agent powered by DSPy" readme = "README.md" license = "MIT" @@ -11,18 +11,19 @@ documentation = "https://github.com/khezen/codespy#readme" keywords = ["code-review", "ai", "dspy", "llm", "github", "pull-request", "security", "bug-detection", "static-analysis"] packages = [{ include = "codespy", from = "src" }] classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] [tool.poetry.dependencies] python = ">=3.11,<3.14" -dspy = {version = "^3.1.3", extras = ["mcp"]} -litellm = "^1.81.6" +dspy = {version = "^3.3.0", extras = ["mcp"]} +litellm = "^1.84.0" cachetools = ">=5.0.0" PyGithub = ">=2.5.0" python-gitlab = ">=4.0.0" @@ -30,11 +31,11 @@ typer = ">=0.12.0" rich = ">=13.9.0" pydantic = ">=2.10.0" pydantic-settings = ">=2.6.0" -gitpython = ">=3.1.0" +gitpython = ">=3.1.41" httpx = ">=0.28.0" boto3 = ">=1.35.0" # Required for AWS Bedrock cloudpickle = "^3.1.2" -json-repair = "^0.55.1" +json-repair = ">=0.56.0" tree-sitter = ">=0.23" tree-sitter-go = ">=0.23" tree-sitter-python = ">=0.23" @@ -46,9 +47,9 @@ tree-sitter-kotlin = ">=1.0" tree-sitter-objc = ">=3.0" tree-sitter-rust = ">=0.23" tree-sitter-hcl = ">=1.2.0" -mcp = ">=1.0.0" +mcp = ">=1.29.0,<2.0.0" beautifulsoup4 = ">=4.12.0" -markdownify = ">=0.13.0" +markdownify = ">=0.14.0" ddgs = ">=8.0.0" [tool.poetry.group.dev.dependencies] diff --git a/src/codespy/__init__.py b/src/codespy/__init__.py index 2512192..6da04e5 100644 --- a/src/codespy/__init__.py +++ b/src/codespy/__init__.py @@ -1,3 +1,3 @@ """codespy - Code review agent powered by DSPy.""" -__version__ = "0.4.1" +__version__ = "1.0.0" diff --git a/src/codespy/agents/context_safe.py b/src/codespy/agents/context_safe.py new file mode 100644 index 0000000..82f2247 --- /dev/null +++ b/src/codespy/agents/context_safe.py @@ -0,0 +1,158 @@ +"""ContextSafe wrapper for context window overflow resilience. + +Provides transparent fallback to RLM (Recursive Language Model) when inputs +exceed the model's context window. Used to wrap all DSPy signature modules. +""" + +import logging +import re + +import dspy # type: ignore[import-untyped] +import litellm # type: ignore[import-untyped] + +logger = logging.getLogger(__name__) + +# Regex for detecting context window overflow in error messages +_RE_CONTEXT_LENGTH = re.compile(r"maximum context length is \d+ tokens", re.IGNORECASE) + + +def estimate_context_overflow(model: str, max_tokens: int, input_text: str) -> bool: + """Estimate whether input + max_tokens would exceed the model's context window. + + Returns True if overflow is likely. Returns False if no overflow expected + or if the model is not in litellm's DB (can't estimate). + """ + SAFETY_MARGIN = 4096 + try: + info = litellm.get_model_info(model) + max_input = info.get("max_input_tokens") or 0 + if not max_input: + return False + estimated_input = litellm.token_counter(model=model, text=input_text) + return (estimated_input + max_tokens + SAFETY_MARGIN) > max_input + except Exception: + return False + + +def is_context_overflow_error(exc: Exception) -> bool: + """Detect context window overflow from any source. + + Handles two cases: + - Known models: dspy raises dspy.ContextWindowExceededError + - Unknown models (nvidia/zai Bedrock): litellm raises BadRequestError, + dspy wraps as LMInvalidRequestError — detected via error message regex. + """ + if isinstance(exc, dspy.ContextWindowExceededError): + return True + return bool(_RE_CONTEXT_LENGTH.search(str(exc))) + + +class ContextSafe(dspy.Module): + """Wraps a dspy.Module and falls back to RLM on context window overflow. + + Two-layer defense: + - Pre-flight: estimates input tokens via litellm; if overflow is predicted + for a known model, skips the inner module and uses RLM directly. + - Try/catch: if the inner module raises a context overflow error (unknown + models where pre-flight can't estimate), catches it and retries with RLM. + + Transparent to Hippocampus — delegates .signature to inner module. + """ + + def __init__(self, module: dspy.Module, signature, tools: list | None = None, name: str = ""): + super().__init__() + self.module = module + self._orig_signature = signature + self._tools = tools + self._name = name or signature.__name__ + + @property + def signature(self): + return getattr(self.module, "signature", None) + + @signature.setter + def signature(self, value): + self.module.signature = value + + def forward(self, **kwargs) -> dspy.Prediction: + if self._would_overflow(kwargs): + logger.warning( + "ContextSafe[%s]: pre-flight detected context overflow for model=%s; " + "falling back to RLM", + self._name, + getattr(dspy.settings.lm, "model", "unknown"), + ) + return self._create_rlm_fallback()(**kwargs) + + try: + return self.module(**kwargs) + except Exception as exc: + if not is_context_overflow_error(exc): + raise + logger.warning( + "ContextSafe[%s]: context window overflow on model=%s; " + "falling back to RLM (error: %s)", + self._name, + getattr(dspy.settings.lm, "model", "unknown"), + str(exc)[:200], + ) + return self._create_rlm_fallback()(**kwargs) + + async def aforward(self, **kwargs) -> dspy.Prediction: + """Async path — used by code_review, scope, supply_chain via Hippocampus.aforward.""" + if self._would_overflow(kwargs): + logger.warning( + "ContextSafe[%s]: pre-flight detected context overflow for model=%s; " + "falling back to RLM", + self._name, + getattr(dspy.settings.lm, "model", "unknown"), + ) + return await self._create_rlm_fallback().acall(**kwargs) + + try: + return await self.module.acall(**kwargs) + except Exception as exc: + if not is_context_overflow_error(exc): + raise + logger.warning( + "ContextSafe[%s]: context window overflow on model=%s; " + "falling back to RLM (error: %s)", + self._name, + getattr(dspy.settings.lm, "model", "unknown"), + str(exc)[:200], + ) + return await self._create_rlm_fallback().acall(**kwargs) + + def _would_overflow(self, kwargs: dict) -> bool: + """Pre-flight: estimate overflow from current LM config and input size.""" + try: + lm = dspy.settings.lm + if lm is None: + return False + model = lm.model + max_tokens = lm.kwargs.get("max_tokens") or 0 + if not max_tokens: + return False + input_text = "\n".join(str(v) for v in kwargs.values()) + return estimate_context_overflow(model, max_tokens, input_text) + except Exception: + return False + + def _get_current_signature(self): + """Get current signature (may include context_memory if Hippocampus modified it).""" + sig = getattr(self.module, "signature", None) + if sig is not None: + return sig + preds = list(self.module.named_predictors()) + if preds: + return preds[0][1].signature + return self._orig_signature + + def _create_rlm_fallback(self) -> dspy.RLM: + """Create RLM with current signature and tools.""" + return dspy.RLM( + self._get_current_signature(), + tools=self._tools, + max_iters=10, + max_llm_calls=20, + ) diff --git a/src/codespy/agents/cost_tracker.py b/src/codespy/agents/cost_tracker.py index a8350fc..a0042d6 100644 --- a/src/codespy/agents/cost_tracker.py +++ b/src/codespy/agents/cost_tracker.py @@ -4,13 +4,19 @@ even during parallel execution with dspy.Parallel. """ +import logging +import sys import threading import time +from contextlib import AbstractContextManager from dataclasses import dataclass -from typing import Optional +from types import TracebackType +from typing import Any import dspy # type: ignore[import-untyped] +logger = logging.getLogger(__name__) + @dataclass class SignatureStats: @@ -20,8 +26,8 @@ class SignatureStats: cost: float = 0.0 tokens: int = 0 call_count: int = 0 - start_time: Optional[float] = None - end_time: Optional[float] = None + start_time: float | None = None + end_time: float | None = None @property def duration_seconds(self) -> float: @@ -44,7 +50,7 @@ def to_dict(self) -> dict: class CostTracker: """Track LLM costs across multiple calls with per-signature attribution. - + Uses DSPy's LM history for per-signature tracking, which works reliably even during parallel execution. """ @@ -61,7 +67,7 @@ def reset(self) -> None: def start_signature(self, signature_name: str) -> None: """Mark the start of a signature's execution. - + Args: signature_name: Name of the signature starting execution """ @@ -73,7 +79,7 @@ def start_signature(self, signature_name: str) -> None: def end_signature(self, signature_name: str, cost: float, tokens: int, call_count: int) -> None: """Mark the end of a signature's execution with its costs. - + Args: signature_name: Name of the signature ending execution cost: Total cost for this signature's LLM calls @@ -107,12 +113,12 @@ def call_count(self) -> int: with self._lock: return sum(s.call_count for s in self._signature_stats.values()) - def get_signature_stats(self, signature_name: str) -> Optional[SignatureStats]: + def get_signature_stats(self, signature_name: str) -> SignatureStats | None: """Get stats for a specific signature. - + Args: signature_name: Name of the signature - + Returns: SignatureStats or None if signature not found """ @@ -121,7 +127,7 @@ def get_signature_stats(self, signature_name: str) -> Optional[SignatureStats]: def get_all_signature_stats(self) -> dict[str, SignatureStats]: """Get stats for all signatures. - + Returns: Dictionary of signature name to SignatureStats """ @@ -139,7 +145,7 @@ def get_all_signature_stats(self) -> dict[str, SignatureStats]: def _get_history_entries() -> list[dict]: """Get current LM history entries from DSPy. - + Returns: List of history entries, or empty list if LM not configured """ @@ -154,7 +160,7 @@ def _get_history_entries() -> list[dict]: def _get_history_uuids() -> set[str]: """Get UUIDs of current history entries. - + Returns: Set of UUIDs from current history """ @@ -162,58 +168,87 @@ def _get_history_uuids() -> set[str]: return {entry.get("uuid", "") for entry in entries if entry.get("uuid")} +def _as_number(value: object) -> float: + """Coerce a history field to a number, yielding 0.0 for anything unusable. + + History entries are raw provider/LiteLLM payloads, so ``cost`` and the + ``usage`` counters are only *conventionally* numeric. Coercing here keeps a + provider-shape surprise from turning accounting into an exception. + + Args: + value: A value read from an LM history entry. + + Returns: + The value as a float, or 0.0 if it is missing or not numeric. + """ + if isinstance(value, bool) or value is None: + return 0.0 + if isinstance(value, (int, float)): + return float(value) + try: # Some providers report numbers as strings. + return float(value) # type: ignore[arg-type] + except (TypeError, ValueError): + return 0.0 + + def _calculate_costs_from_entries(entries: list[dict], exclude_uuids: set[str]) -> tuple[float, int, int]: """Calculate costs from history entries, excluding specific UUIDs. - + + Every field is read defensively: cost accounting is observability, so a + malformed entry degrades that entry to zero rather than failing the review + that produced it. + Args: entries: List of history entries exclude_uuids: Set of UUIDs to exclude from calculation - + Returns: Tuple of (total_cost, total_tokens, call_count) """ total_cost = 0.0 total_tokens = 0 call_count = 0 - + for entry in entries: + if not isinstance(entry, dict): + continue + entry_uuid = entry.get("uuid", "") if entry_uuid and entry_uuid not in exclude_uuids: - # Get cost - cost = entry.get("cost") - if cost is not None: - total_cost += cost - + total_cost += _as_number(entry.get("cost")) + # Get tokens from usage - usage = entry.get("usage", {}) - if usage: - prompt_tokens = usage.get("prompt_tokens", 0) or 0 - completion_tokens = usage.get("completion_tokens", 0) or 0 - total_tokens += prompt_tokens + completion_tokens - + usage = entry.get("usage") + if isinstance(usage, dict): + total_tokens += int(_as_number(usage.get("prompt_tokens"))) + total_tokens += int(_as_number(usage.get("completion_tokens"))) + call_count += 1 - + return total_cost, total_tokens, call_count class SignatureContext: - """Context manager for tracking signature execution. - - Uses DSPy's LM history mechanism to track costs reliably, even during - parallel execution with dspy.Parallel. Works by: - 1. Recording history UUIDs before signature execution - 2. After execution, finding new entries (by UUID) - 3. Summing costs/tokens from new entries - + """Context manager scoping one named unit of LLM work. + + Two responsibilities, both keyed off the same name: + + 1. **LM selection** — applies the model, temperature, and reasoning effort + configured for this name (``signatures.`` or ``memory.``), + falling back to the top-level defaults. + 2. **Cost attribution** — uses DSPy's LM history to attribute costs + reliably, even during parallel execution with dspy.Parallel, by + recording history UUIDs on entry and summing only the new entries. + Usage: with SignatureContext("code_review", cost_tracker): - # All LLM calls here will be attributed to code_review + # Runs on code_review's configured model, costs attributed to it result = await agent.acall(...) """ - + def __init__(self, signature_name: str, tracker: "CostTracker") -> None: """Initialize the signature context. - + Args: signature_name: Name of the signature tracker: CostTracker instance @@ -221,27 +256,77 @@ def __init__(self, signature_name: str, tracker: "CostTracker") -> None: self.signature_name = signature_name self.tracker = tracker self._before_uuids: set[str] = set() + # Annotated so the None default doesn't narrow the attribute to + # ``None``, which would hide the enter/exit calls from type checking. + self._lm_context: AbstractContextManager[Any] | None = None def __enter__(self) -> "SignatureContext": - """Enter the context, capturing current history state.""" - # Capture UUIDs of entries that exist before signature execution - self._before_uuids = _get_history_uuids() - self.tracker.start_signature(self.signature_name) + """Enter the context, applying the LM and capturing history state. + + If the bookkeeping that follows the LM swap fails, the swap is rolled + back before propagating: Python does not call ``__exit__`` when + ``__enter__`` raises, so without this the overridden LM would stay + installed for the rest of the thread. + """ + # Imported here to avoid a circular import at module load time + # (dspy_config imports codespy.config, which must not import agents). + from codespy.agents.dspy_config import lm_context + + # Apply this name's LM first, so the history we snapshot below belongs + # to the LM that will actually serve the enclosed calls. + self._lm_context = lm_context(self.signature_name) + self._lm_context.__enter__() + try: + self._before_uuids = _get_history_uuids() + self.tracker.start_signature(self.signature_name) + except BaseException: + self._lm_context.__exit__(*sys.exc_info()) + self._lm_context = None + raise return self - def __exit__(self, exc_type, exc_val, exc_tb) -> None: - """Exit the context, calculating costs from new history entries.""" - # Get all current entries and calculate costs from new ones - entries = _get_history_entries() - cost, tokens, call_count = _calculate_costs_from_entries(entries, self._before_uuids) - - self.tracker.end_signature(self.signature_name, cost, tokens, call_count) + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + """Exit the context, calculating costs from new history entries. + + Cost calculation failures are logged at WARNING level but never + propagated — bookkeeping must not mask application errors. + + The LM context is released in a ``finally``: a leaked + ``dspy.context`` does not raise, it silently leaves the overridden LM + installed for the remainder of the thread, so every later predictor + would run on the wrong model and be attributed to the wrong signature. + Guaranteeing the exit keeps an accounting failure loud and local + instead of quiet and global. + """ + try: + # Read history before leaving the LM context, so dspy.settings.lm + # still points at the LM whose history we need. + entries = _get_history_entries() + cost, tokens, call_count = _calculate_costs_from_entries(entries, self._before_uuids) + self.tracker.end_signature(self.signature_name, cost, tokens, call_count) + except Exception as e: + logger.warning("Cost calculation failed for %s: %s", self.signature_name, e) + finally: + if self._lm_context is not None: + self._lm_context.__exit__(exc_type, exc_val, exc_tb) + self._lm_context = None + async def __aenter__(self) -> "SignatureContext": """Async enter the context.""" return self.__enter__() - async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: """Async exit the context.""" self.__exit__(exc_type, exc_val, exc_tb) @@ -252,4 +337,4 @@ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: def get_cost_tracker() -> CostTracker: """Get the global cost tracker instance.""" - return _cost_tracker \ No newline at end of file + return _cost_tracker diff --git a/src/codespy/agents/dspy_config.py b/src/codespy/agents/dspy_config.py index 66f3ea8..82bdafd 100644 --- a/src/codespy/agents/dspy_config.py +++ b/src/codespy/agents/dspy_config.py @@ -3,15 +3,140 @@ import logging import dspy # type: ignore[import-untyped] -from dspy.adapters.two_step_adapter import TwoStepAdapter # type: ignore[import-untyped] import litellm # type: ignore[import-untyped] +from dspy.adapters.two_step_adapter import TwoStepAdapter # type: ignore[import-untyped] -from codespy.config import Settings +from codespy.config import Settings, get_settings +from codespy.config_memory import REFLECTION_MODULES, LLMSettings logger = logging.getLogger(__name__) +def _resolve_max_tokens(model: str, max_tokens: int) -> int: + """Clamp a configured output budget to the model's real output ceiling. + + ``max_tokens`` is an *output* budget, not a context window, and providers + reject a request that asks for more than the model can emit (Anthropic + returns a 400). Clamping lets a single generous default be configured + globally and still resolve to a valid value per model. + + Models LiteLLM doesn't know about (Ollama, custom endpoints) have no + published ceiling, so the configured value is passed through unchanged. + + Args: + model: The LiteLLM model identifier. + max_tokens: The configured output token budget. + + Returns: + The budget, reduced to the model's ceiling when one is known. + """ + try: + ceiling = litellm.get_max_tokens(model) + except Exception: # Unmapped model — no published ceiling to clamp to. + return max_tokens + if not ceiling: + return max_tokens + return min(max_tokens, ceiling) + + +def _supports_cache_control(model: str) -> bool: + """Whether the model uses explicit Anthropic-style cache_control markers. + + Returns True only for models where the provider expects ``cache_control`` + fields in messages to enable prompt caching. Models with automatic caching + (OpenAI prefix-match, Gemini) or no caching return False — sending markers + to them is either pointless or causes provider errors. + + Detection: ``cache_creation_input_token_cost`` is non-None in LiteLLM's + model database only for Anthropic-style providers that charge separately + for cache writes, which correlates exactly with explicit marker support. + + Falls back to provider-prefix heuristic when model info is unavailable. + """ + try: + info = litellm.get_model_info(model) + return info.get("cache_creation_input_token_cost") is not None + except Exception: + # Model not in LiteLLM DB (Ollama offline, custom endpoint). + # Fall back to prefix heuristic. + lower = model.lower() + if lower.startswith("anthropic/"): + return True + return bool(lower.startswith("bedrock/") and "anthropic" in lower) + + +def new_lm(settings: Settings, config: LLMSettings) -> dspy.LM: + """Build a ``dspy.LM`` for resolved LLM settings. + + The single place LMs are constructed, so the cross-cutting concerns + (timeout, retries, output budget, provider-side prompt caching) are applied + uniformly. + + ``reasoning_effort`` is always forwarded to LiteLLM through ``dspy.LM``'s + ``**kwargs``; LiteLLM maps it onto each provider's native parameter + (Anthropic ``thinking.budget_tokens``, OpenAI ``reasoning_effort``, + Ollama ``think``, ...). ``drop_params=True`` ensures that models/providers + which do not support it gracefully ignore the parameter instead of crashing. + + ``max_tokens`` must be passed explicitly: omitting it makes LiteLLM fall + back to its own 4096 default, which silently truncates responses (DSPy then + warns about ``max_tokens=None``). Reasoning tokens are charged against this + budget, so it is clamped to — not capped below — the model's ceiling. + + Args: + settings: Application settings (timeout / retries / prompt caching). + config: Resolved settings from ``Settings.get_llm_config()``. + + Returns: + A configured ``dspy.LM``. + """ + lm_kwargs: dict = { + "model": config.model, + "temperature": config.temperature, + "max_tokens": _resolve_max_tokens(config.model, config.max_tokens), + "timeout": settings.llm_timeout, + "num_retries": settings.llm_retries, + "drop_params": True, + "reasoning_effort": config.reasoning_effort, + } + # Cache system prompts via explicit Anthropic-style cache_control markers. + # Only injected for providers that use explicit markers (Anthropic, Bedrock + # Anthropic); OpenAI/Gemini have automatic caching that needs no markers. + if settings.enable_prompt_caching and _supports_cache_control(config.model): + lm_kwargs["cache_control_injection_points"] = [ + {"location": "message", "role": "system"} + ] + elif settings.enable_prompt_caching: + logger.debug( + f"Prompt caching enabled but model {config.model} does not use " + f"explicit cache_control markers — skipping injection" + ) + return dspy.LM(**lm_kwargs) + + +def lm_context(name: str): + """Return a ``dspy.context`` applying the LM configured for ``name``. + + ``name`` is a signature or reflection module name — see + ``Settings.get_llm_config``. Enter this around the predictor call so the + named unit of work runs on its own configured model, falling back to the + top-level defaults when it declares no overrides. + + Args: + name: The signature or reflection module name. + + Returns: + A context manager that scopes the LM to the enclosed block. + """ + settings = get_settings() + llm_config = settings.get_llm_config(name) + lm = new_lm(settings, llm_config) + return dspy.context(lm=lm) + + + def configure_dspy(settings: Settings) -> None: + """Configure DSPy with the LLM backend for reliable structured output. This configures DSPy with: @@ -44,33 +169,24 @@ def configure_dspy(settings: Settings) -> None: if settings.aws_secret_access_key: os.environ["AWS_SECRET_ACCESS_KEY"] = settings.aws_secret_access_key - # Build LM kwargs with reliability settings - lm_kwargs: dict = { - "model": model, - "timeout": settings.llm_timeout, # Global timeout (default: 120s) - "num_retries": settings.llm_retries, # Global retries (default: 3) - } + # Global fallback LM, used by any predictor not wrapped in lm_context(). + # An unknown name resolves purely from the top-level defaults. + defaults = settings.get_llm_config("default") + lm = new_lm(settings, defaults) - # Enable provider-side prompt caching if configured - # This caches system prompts on the LLM provider's servers (Anthropic, OpenAI, Bedrock, etc.) - if settings.enable_prompt_caching: - lm_kwargs["cache_control_injection_points"] = [ - {"location": "message", "role": "system"} - ] - - # Configure DSPy with LiteLLM and TwoStepAdapter - lm = dspy.LM(**lm_kwargs) - - # Create extraction LM for TwoStepAdapter's second stage - # Uses a smaller/faster model to extract structured fields from free-form responses - # Falls back to default_model if no extraction_model is configured - extraction_model = settings.extraction_model or settings.default_model - extraction_lm = dspy.LM( - model=extraction_model, - timeout=settings.llm_timeout, - num_retries=settings.llm_retries, + # Extraction LM for TwoStepAdapter's second stage: deterministic field extraction + # from the main LM's free-form response. Never uses reasoning; temperature=0.0 for + # deterministic output. + extraction_model = defaults.extraction_model + extraction_lm = new_lm( + settings, defaults.model_copy(update={ + "model": extraction_model, + "reasoning_effort": None, + "temperature": 0.0, + }) ) + dspy.settings.configure( lm=lm, adapter=TwoStepAdapter(extraction_lm), # TwoStepAdapter solves ChatAdapter parsing failures @@ -79,10 +195,16 @@ def configure_dspy(settings: Settings) -> None: # Enable memory-only caching for LLM calls (no disk caching) dspy.configure_cache(enable_memory_cache=True, enable_disk_cache=False, memory_max_entries=10000) - prompt_cache_status = "enabled" if settings.enable_prompt_caching else "disabled" + if not settings.enable_prompt_caching: + prompt_cache_status = "disabled" + elif _supports_cache_control(model): + prompt_cache_status = "enabled (cache_control markers)" + else: + prompt_cache_status = "enabled (provider-automatic, no markers)" logger.info( f"Configured DSPy with model: {model} " f"(TwoStepAdapter with extraction_model={extraction_model}, " + f"max_tokens={_resolve_max_tokens(defaults.model, defaults.max_tokens)}, " f"timeout={settings.llm_timeout}s, retries={settings.llm_retries}, " f"provider prompt caching {prompt_cache_status})" ) @@ -91,7 +213,9 @@ def configure_dspy(settings: Settings) -> None: def verify_model_access(settings: Settings) -> tuple[bool, str]: """Verify that all configured models are accessible. - Checks the default model and all per-signature model overrides. + Checks the default model, all per-signature model overrides, and the + memory reflection models, so a typo in any of them fails fast at startup + rather than mid-review. Args: settings: Application settings containing model configuration. @@ -101,16 +225,25 @@ def verify_model_access(settings: Settings) -> tuple[bool, str]: """ # Collect all unique models from config models_to_check: set[str] = {settings.default_model} - + # Check all signature-specific models - for sig_name, sig_config in settings.signatures.items(): + for _sig_name, sig_config in settings.signatures.items(): if sig_config.model: models_to_check.add(sig_config.model) - + + # Global extraction model (if different from default_model) + if settings.extraction_model: + models_to_check.add(settings.extraction_model) + + for module in REFLECTION_MODULES: + reflection = settings.get_llm_config(module) + models_to_check.add(reflection.model) + + # Check each model verified: list[str] = [] failed: list[str] = [] - + for model in models_to_check: try: litellm.completion( @@ -128,26 +261,24 @@ def verify_model_access(settings: Settings) -> tuple[bool, str]: failed.append(f"{model}: connection error - {e}") except Exception as e: failed.append(f"{model}: {e}") - + if failed: return False, f"Model verification failed: {'; '.join(failed)}" - + return True, f"Verified {len(verified)} model(s): {', '.join(verified)}" class _TaskDestroyedFilter(logging.Filter): """Filter to suppress 'Task was destroyed' messages from asyncio.""" - + def filter(self, record: logging.LogRecord) -> bool: msg = record.getMessage() - if "Task was destroyed" in msg and "LoggingWorker" in msg: - return False - return True + return not ("Task was destroyed" in msg and "LoggingWorker" in msg) class _MCPRequestFilter(logging.Filter): """Filter to suppress all noisy 'Processing request of type' MCP server messages.""" - + def filter(self, record: logging.LogRecord) -> bool: return "Processing request of type" not in record.getMessage() @@ -157,4 +288,4 @@ def filter(self, record: logging.LogRecord) -> bool: # Suppress noisy MCP server "Processing request" messages logging.getLogger("mcp.server").addFilter(_MCPRequestFilter()) -logging.getLogger("mcp.server.lowlevel").addFilter(_MCPRequestFilter()) \ No newline at end of file +logging.getLogger("mcp.server.lowlevel").addFilter(_MCPRequestFilter()) diff --git a/src/codespy/agents/memory/__init__.py b/src/codespy/agents/memory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/codespy/agents/memory/hippocampus/__init__.py b/src/codespy/agents/memory/hippocampus/__init__.py new file mode 100644 index 0000000..3da84ee --- /dev/null +++ b/src/codespy/agents/memory/hippocampus/__init__.py @@ -0,0 +1,40 @@ +from codespy.agents.memory.hippocampus.budget import MemoryBudget +from codespy.agents.memory.hippocampus.context_memory import ( + CacheCandidate, + ContextMemory, + Item, + ItemTag, + Mutation, + Operation, + OpType, + SectionName, + Topic, + compute_common_ancestor_topic_id, + make_topic_id, +) +from codespy.agents.memory.hippocampus.episode import Episode, find_latest_episode +from codespy.agents.memory.hippocampus.hippocampus import Hippocampus +from codespy.agents.memory.hippocampus.modules.cartographer import Cartographer, CartographerSig +from codespy.agents.memory.hippocampus.modules.distiller import Distiller, DistillerSig + +__all__ = [ + "CacheCandidate", + "Cartographer", + "CartographerSig", + "ContextMemory", + "Distiller", + "DistillerSig", + "Episode", + "find_latest_episode", + "Hippocampus", + "Item", + "ItemTag", + "MemoryBudget", + "Mutation", + "Operation", + "OpType", + "SectionName", + "Topic", + "compute_common_ancestor_topic_id", + "make_topic_id", +] diff --git a/src/codespy/agents/memory/hippocampus/budget.py b/src/codespy/agents/memory/hippocampus/budget.py new file mode 100644 index 0000000..f6e2863 --- /dev/null +++ b/src/codespy/agents/memory/hippocampus/budget.py @@ -0,0 +1,332 @@ +from __future__ import annotations + +from dataclasses import dataclass + +import dspy +import tiktoken + +from codespy.agents.memory.hippocampus.context_memory import ContextMemory + +_ENCODING = tiktoken.get_encoding("o200k_base") + + +# --------------------------------------------------------------------------- +# Budget +# --------------------------------------------------------------------------- + +@dataclass(frozen=True) +class MemoryBudget: + """The four token budgets bounding Hippocampus memory. + + These four always travel together: a signature's memory is configured as a + set, not field by field. All are measured with the ``o200k_base`` encoding. + + Frozen because they are read-only configuration — a budget can be resolved + once (see ``Settings.get_memory_budget``) and shared across instances. + + Attributes: + max_context_memory_tokens: Hard ceiling on the rendered ContextMemory, + enforced by the Evictor after every reflection. This is the + *persisted* artifact and it is prepended to every predictor of the + wrapped agent, so it is re-sent on every agent iteration + (~``max_iters`` times per run) plus once per reflection call — the + most cost-sensitive of the four. Divided by ``max_context_item_tokens`` it + gives the memory's approximate item capacity (8192 / 410 ~= 19 items). + max_context_item_tokens: Budget for a *single* context memory item, passed to the + Distiller and the Cartographer as a prompt input so they keep each + item compact rather than spending the whole memory budget on one + verbose entry. Unlike the other three this is a **soft** budget: + expressed to the LLM, not enforced in code, since truncating an + item could corrupt an exact constant it holds. Lower it to fit + more, terser items in the same memory budget; raise it for richer + items. + max_trajectory_tokens: Budget for trajectories fed to the Distiller. + None = full trajectory, Distiller does all compression — only + viable for agents with short, predictable trajectories. Tool-using + agents can produce 100k+ token trajectories, and TwoStepAdapter + sends the value twice, so prefer ~5–10 % of the Distiller model's + context window (default 8192, i.e. ~5 % of 128k). Applied per call + (stage 1) and again over the combined episode in end_episode() + (stage 2). Step-aware head+tail bounding (60 % head / 40 % tail) + preserves both setup and conclusions. + max_question_tokens: Budget for the serialized inputs used as the + reflection "question", on the fallback path when + ``Hippocampus.question`` is None. None = unbounded, which is + rarely safe: *every* input field is serialized, so an agent taking + a large field (a document dump, or a diff of every changed file) + sends all of it to both the Distiller and the Cartographer. + Ignored when ``question`` is set — prefer that when a single + field cleanly captures intent. + """ + + max_context_memory_tokens: int = 8192 + max_context_item_tokens: int = 410 + max_trajectory_tokens: int | None = 8192 + max_question_tokens: int | None = 2048 + + +# Eviction priority — lower number = evict first +_SECTION_EVICT_PRIORITY: dict[str, int] = { + "parsing_schema": 0, # evict first — cheap to rediscover + "reusable_results": 1, # agent-derived; can be recomputed + "domain_constants": 2, # exact values worth protecting + "context_roadmap": 3, # protected — structural index + "context_understanding": 4, # most protected — core orientation +} + + +# --------------------------------------------------------------------------- +# Token counting +# --------------------------------------------------------------------------- + +def count_tokens(s: str) -> int: + return len(_ENCODING.encode(s)) + + +# --------------------------------------------------------------------------- +# Input serialisation (for the question field sent to the Distiller) +# --------------------------------------------------------------------------- + +def format_inputs(kwargs: dict, max_tokens: int | None = None) -> str: + """Serialize call inputs (excluding context_memory) for the Distiller question. + + All fields are included in full. If max_tokens is set, the joined result is + head+tail bounded via _head_tail_text so both the instruction and any + trailing intent survive. See MemoryBudget.max_question_tokens for guidance + on when and how to set a limit. + """ + parts: list[str] = [] + for k, v in kwargs.items(): + if k == "context_memory": + continue + parts.append(f"{k}: {v}") + text = "\n".join(parts) + if max_tokens is None: + return text + return _head_tail_text(text, max_tokens) + + +# --------------------------------------------------------------------------- +# Eviction +# --------------------------------------------------------------------------- + +def evict(context_memory: ContextMemory, scores: dict[str, int], budget: int) -> ContextMemory: + if count_tokens(context_memory.render()) <= budget: + return context_memory + item_section: dict[str, str] = { + it.id: sec for sec in context_memory.section_names() for it in context_memory.section(sec) + } + flat = context_memory.all_items() + order = {it.id: i for i, it in enumerate(flat)} + victims = sorted( + flat, + key=lambda it: ( + _SECTION_EVICT_PRIORITY.get(item_section[it.id], 99), + scores.get(it.id, 0), + order[it.id], + ), + ) + removed: set[str] = set() + for v in victims: + removed.add(v.id) + trial = context_memory.without(removed) + if count_tokens(trial.render()) <= budget: + return trial + return context_memory.without(removed) + + +# --------------------------------------------------------------------------- +# Trajectory formatting with optional step-aware head+tail bounding +# --------------------------------------------------------------------------- + +def _format_step(i: int, entry: dict) -> str: + parts = [f"--- Step {i + 1} ---"] + if entry.get("reasoning"): + parts.append(f"Reasoning: {entry['reasoning']}") + code = entry.get("code", "") + if code: + parts.append(f"Code:\n{code}") + output = entry.get("output", "") + if output: + parts.append(f"Output:\n{output}") + return "\n".join(parts) + + +# Tokens set aside for the "... (N tokens omitted) ..." marker so the returned +# text honours max_tokens including the marker itself. +_MARKER_RESERVE = 16 + + +def _slice_tokens(text: str, n: int, from_end: bool = False) -> str: + """Return the first (or last) ``n`` tokens of ``text`` as a string. + + Used to split a line that is itself larger than the remaining budget, so + line-granularity bounding never has to discard a line wholesale. + """ + if n <= 0: + return "" + tokens = _ENCODING.encode(text) + kept = tokens[-n:] if from_end else tokens[:n] + return _ENCODING.decode(kept) + + +def _head_tail_text(text: str, max_tokens: int, head_ratio: float = 0.6) -> str: + """Keep the first head_ratio and last (1-head_ratio) of the token budget, + dropping the middle with an omission marker. + + Prefers line granularity, but falls back to token granularity for a line + that alone exceeds the remaining budget — without that fallback, a single + long line (e.g. the one-line repr of a pydantic input field) would blow the + whole budget and return almost nothing. Returns text unchanged if it fits. + + The omission marker is charged against ``max_tokens`` (see _MARKER_RESERVE), + so the result never exceeds the budget. + """ + if count_tokens(text) <= max_tokens: + return text + + content_budget = max(max_tokens - _MARKER_RESERVE, 0) + head_budget = int(content_budget * head_ratio) + tail_budget = content_budget - head_budget + + lines = text.splitlines(keepends=True) + + # Collect head lines, token-slicing the line that straddles the budget. + head_lines: list[str] = [] + head_tokens = 0 + for line in lines: + lt = count_tokens(line) + if head_tokens + lt > head_budget: + head_lines.append(_slice_tokens(line, head_budget - head_tokens)) + head_tokens = head_budget + break + head_lines.append(line) + head_tokens += lt + + # Collect tail lines (from the end), same straddle handling. + tail_lines: list[str] = [] + tail_tokens = 0 + for line in reversed(lines): + lt = count_tokens(line) + if tail_tokens + lt > tail_budget: + tail_lines.append( + _slice_tokens(line, tail_budget - tail_tokens, from_end=True) + ) + tail_tokens = tail_budget + break + tail_lines.append(line) + tail_tokens += lt + tail_lines.reverse() + + total = count_tokens(text) + omitted = total - head_tokens - tail_tokens + marker = f"... ({omitted} tokens omitted) ...\n" + + return "".join(head_lines) + marker + "".join(tail_lines) + + +def format_trajectory(pred: dspy.Prediction, max_tokens: int | None = None) -> str: + """Serialize a dspy trajectory to text, with optional head+tail bounding. + + Args: + pred: The dspy Prediction returned by the wrapped agent. + max_tokens: If None (default), the full trajectory is returned so the + Distiller can do all compression. If set, step-aware head+tail + bounding is applied: whole steps are kept from both the front and + back of the trajectory (60 % head / 40 % tail), and the middle is + replaced by an omission marker. For dict / fallback trajectories, + the same head+tail logic is applied at line granularity. A single + oversized step's Output block is itself head+tail bounded before + the per-step budget accounting. + + Trajectory shapes handled: + list — ReAct / CodeAct: list of dicts with 'code', 'output', + optional 'reasoning'. Step-aware bounding. + dict — flat key/value dump. Line-granularity bounding. + other — str(pred) or pred.toDict() fallback. Line-granularity bounding. + """ + traj = getattr(pred, "trajectory", None) + + # ----- list path (ReAct / CodeAct) ----- + if isinstance(traj, list): + step_texts = [_format_step(i, entry) for i, entry in enumerate(traj)] + + if max_tokens is None: + return "\n\n".join(step_texts) + + # Check if everything fits as-is + full = "\n\n".join(step_texts) + if count_tokens(full) <= max_tokens: + return full + + content_budget = max(max_tokens - _MARKER_RESERVE, 0) + head_budget = int(content_budget * 0.6) + tail_budget = content_budget - head_budget + + # Cap individual oversized step outputs before budgeting + capped: list[str] = [] + for s in step_texts: + if count_tokens(s) > max_tokens: + s = _head_tail_text(s, max_tokens) + capped.append(s) + + # Greedily keep head steps + head_steps: list[str] = [] + head_tokens = 0 + for s in capped: + t = count_tokens(s) + if head_tokens + t > head_budget: + break + head_steps.append(s) + head_tokens += t + + # Greedily keep tail steps (from the end), never reusing a head step + tail_steps: list[str] = [] + tail_tokens = 0 + for s in reversed(capped[len(head_steps):]): + t = count_tokens(s) + if tail_tokens + t > tail_budget: + break + tail_steps.append(s) + tail_tokens += t + tail_steps.reverse() + + # If no whole step fits in either half (every step is larger than its + # budget), fall back to bounding single steps so the budget is actually + # used instead of returning just the omission marker. + if not head_steps and capped: + head_steps = [_head_tail_text(capped[0], head_budget)] + if not tail_steps and len(capped) > len(head_steps): + tail_steps = [_head_tail_text(capped[-1], tail_budget)] + + # Determine omitted range + n_head = len(head_steps) + n_tail = len(tail_steps) + n_total = len(capped) + n_omitted = n_total - n_head - n_tail + + parts = list(head_steps) + if n_omitted > 0: + first_omitted = n_head + 1 + last_omitted = n_total - n_tail + parts.append( + f"--- Steps {first_omitted}–{last_omitted} omitted ({n_omitted} steps) ---" + ) + parts.extend(tail_steps) + return "\n\n".join(parts) + + # ----- dict path ----- + if isinstance(traj, dict): + text = "\n".join(f"{k}: {v}" for k, v in traj.items()) + if max_tokens is None: + return text + return _head_tail_text(text, max_tokens) + + # ----- fallback ----- + try: + text = "\n".join(f"{k}: {v}" for k, v in pred.toDict().items()) + except Exception: + text = str(pred) + if max_tokens is None: + return text + return _head_tail_text(text, max_tokens) diff --git a/src/codespy/agents/memory/hippocampus/context_memory.py b/src/codespy/agents/memory/hippocampus/context_memory.py new file mode 100644 index 0000000..402a2e7 --- /dev/null +++ b/src/codespy/agents/memory/hippocampus/context_memory.py @@ -0,0 +1,435 @@ +from __future__ import annotations + +import logging +import os +import uuid +from enum import StrEnum +from typing import Literal + +from pydantic import BaseModel, Field + +logger = logging.getLogger(__name__) + + +class ItemTag(StrEnum): + """How a context-memory item performed in the trajectory just observed. + + - helpful: directly aided orientation or answering; keep. + - harmful: misled the agent or contradicted observations; remove. + - neutral: present but unused this round; keep with no boost. + - stale: no longer reflects the external context; remove. + """ + + HELPFUL = "helpful" + HARMFUL = "harmful" + NEUTRAL = "neutral" + STALE = "stale" + + +class OpType(StrEnum): + """Cartographer edit operations against the context memory.""" + + ADD = "ADD" + DELETE = "DELETE" + REPLACE = "REPLACE" + + +SectionName = Literal[ + "context_roadmap", + "context_understanding", + "domain_constants", + "parsing_schema", + "reusable_results", +] + +# Abbreviated prefixes +_SECTION_PREFIX: dict[str, str] = { + "context_roadmap": "cr", + "context_understanding": "cu", + "domain_constants": "dc", + "parsing_schema": "ps", + "reusable_results": "rr", +} + + +class Topic(BaseModel): + """A topic representing a scope in the repository. + + Topics are used to group context items by their relevant scope. + """ + + id: str = Field(description="Topic identifier (e.g., 'owner/repo/package-name')") + description: str = Field(description="Description of this topic's role") + dependencies: list[str] = Field( + default_factory=list, + description="Topic IDs of this topic's dependencies" + ) + + +class Item(BaseModel): + """A single item in the context memory.""" + + id: str = Field(description="Unique item identifier") + content: str = Field(description="Item content") + topic_ids: list[str] = Field(default_factory=list, description="IDs of topics this item belongs to") + + def bind_topics(self, topic_ids: list[str]) -> None: + """Bind this item to the given topic_ids (only if currently unbound).""" + if not self.topic_ids: + self.topic_ids = list(topic_ids) + + +class CacheCandidate(BaseModel): + """A candidate item to be added to the context memory.""" + + section: SectionName = Field(description="Section to add the item to") + value: str = Field(description="Compact candidate cache item, within the max_context_item_tokens budget.") + transferability: str = Field(description="Kinds of future questions this would help.") + rationale: str = Field(description="Why this is shared understanding, not a one-off fact.") + + +class Operation(BaseModel): + """A single edit operation against the context memory.""" + + type: OpType = Field(description="Type of operation") + section: SectionName | None = Field(default=None, description="Required for ADD.") + item_id: str | None = Field(default=None, description="Required for DELETE / REPLACE.") + content: str | None = Field(default=None, description="Required for ADD / REPLACE.") + + +class Mutation(BaseModel): + """A recorded Cartographer mutation applied to the context memory. + + Tracks the sequence of ADD/DELETE/REPLACE operations with pre-mutation + state for debugging and audit purposes. + """ + + step: int = Field(description="Which _distill() pass produced this mutation (0-indexed)") + type: OpType = Field(description="Type of mutation: ADD, DELETE, or REPLACE") + item_id: str = Field(description="Generated ID (ADD) or existing ID (DELETE/REPLACE)") + section: SectionName = Field(description="Section the item belongs to") + content: str | None = Field(default=None, description="New content (ADD/REPLACE); None for DELETE") + previous_content: str | None = Field(default=None, description="Old content (DELETE/REPLACE); None for ADD") + topic_ids: list[str] = Field(default_factory=list, description="Topic IDs associated with this mutation") + + +class ContextMemory(BaseModel): + """Context memory with topics and sectioned items. + + Topic-aware structure where each Item links to one or more topics via topic_ids. + """ + + topics: list[Topic] = Field(default_factory=list, description="Topics representing repo scopes") + context_roadmap: list[Item] = Field( + default_factory=list, + description="Index of what the context contains and where to find it", + ) + context_understanding: list[Item] = Field( + default_factory=list, + description="High-level understanding of the context", + ) + domain_constants: list[Item] = Field( + default_factory=list, + description=( + "Exact parameters, formulas, thresholds, reference values, " + "enum sets, and output field requirements" + ), + ) + parsing_schema: list[Item] = Field( + default_factory=list, + description=( + "How to parse and navigate the context's format: " + "delimiters, boundary patterns, field structure" + ), + ) + reusable_results: list[Item] = Field( + default_factory=list, + description=( + "Agent-derived aggregated outputs (counts, distributions, classifications) " + "that multiple questions would need" + ), + ) + + @classmethod + def section_names(cls) -> list[str]: + """Return list of section field names (excluding 'topics').""" + return [f for f in cls.model_fields if f != "topics"] + + def section(self, name: str) -> list[Item]: + """Get items from a section by name.""" + return getattr(self, name) + + def bind_topics(self, topics: list[Topic], default_topic_ids: list[str]) -> None: + """Set topics and bind all untagged items to default_topic_ids. + + Used by the scope resolver after topics are computed post-hoc + (chicken-and-egg: Hippocampus runs before topics are known). + + Args: + topics: Full list of Topic objects to set on this memory. + default_topic_ids: topic_ids to assign to any item with empty topic_ids. + """ + self.topics = topics + for sec in self.section_names(): + for item in self.section(sec): + item.bind_topics(default_topic_ids) + + def all_items(self) -> list[Item]: + """Return all items across all sections.""" + return [it for s in self.section_names() for it in self.section(s)] + + def find_item(self, item_id: str) -> tuple[SectionName, Item] | None: + """Look up an item by ID across all sections. + + Returns: + Tuple of (section_name, item) if found, None otherwise. + """ + for sec in self.section_names(): + for it in self.section(sec): + if it.id == item_id: + return sec, it # type: ignore[return-value] + return None + + def ids(self) -> set[str]: + """Return set of all item IDs.""" + return {it.id for it in self.all_items()} + + def render(self) -> str: + """Render the context memory as topic-grouped text for LLM consumption. + + Returns: + Topic-grouped text with items organized under their respective topics. + Returns empty string if ContextMemory is completely empty (no topics with items). + """ + # Build topic ID -> topic map + topic_map: dict[str, Topic] = {t.id: t for t in self.topics} + + # Categorize items + shared_items: list[tuple[SectionName, Item]] = [] + topic_items: dict[str, list[tuple[SectionName, Item]]] = {} + + for sec_name in self.section_names(): + for item in self.section(sec_name): + if not item.topic_ids or len(item.topic_ids) != 1: + # Empty or multiple topics -> SHARED + shared_items.append((sec_name, item)) + else: + topic_id = item.topic_ids[0] + if topic_id in topic_map: + if topic_id not in topic_items: + topic_items[topic_id] = [] + topic_items[topic_id].append((sec_name, item)) + else: + # Unknown topic_id -> SHARED + shared_items.append((sec_name, item)) + + # Check if completely empty + if not shared_items and not topic_items: + return "" + + lines: list[str] = [] + + # Render SHARED group first + if shared_items: + lines.append("## SHARED") + lines.extend(self._render_items_by_section(shared_items)) + lines.append("") + + # Render topic groups in order they appear in topics list + for topic in self.topics: + if topic.id in topic_items: + items = topic_items[topic.id] + lines.append(f"## TOPIC: {topic.id} ({topic.description})") + lines.extend(self._render_items_by_section(items)) + lines.append("") + + return "\n".join(lines).rstrip() + "\n" + + def _render_items_by_section( + self, items: list[tuple[SectionName, Item]] + ) -> list[str]: + """Render items grouped by section. + + Args: + items: List of (section_name, item) tuples + + Returns: + List of formatted lines + """ + # Group by section + by_section: dict[str, list[Item]] = {} + for sec_name, item in items: + if sec_name not in by_section: + by_section[sec_name] = [] + by_section[sec_name].append(item) + + lines: list[str] = [] + # Render in section order (as defined in section_names) + for sec_name in self.section_names(): + if sec_name in by_section: + sec_items = by_section[sec_name] + if sec_items: + sec_display = sec_name.upper().replace("_", " ") + lines.append(f"### {sec_display}") + for item in sec_items: + lines.append(f"[{item.id}] {item.content}") + return lines + + def apply(self, ops: list[Operation], topic_ids: list[str] | None = None) -> tuple[ContextMemory, list[str]]: + """Apply operations to create a new ContextMemory. + + Args: + ops: List of operations to apply (ADD, DELETE, REPLACE) + topic_ids: Optional list of topic IDs to assign to new items + + Returns: + Tuple of (new ContextMemory, list of IDs of newly-added items) + """ + cm = self.model_copy(deep=True) + new_ids: list[str] = [] + + for op in ops: + if op.type == OpType.DELETE and op.item_id: + for sec in cm.section_names(): + lst = cm.section(sec) + lst[:] = [it for it in lst if it.id != op.item_id] + + elif op.type == OpType.REPLACE and op.item_id and op.content: + replaced = False + for sec in cm.section_names(): + lst = cm.section(sec) + for i, it in enumerate(lst): + if it.id == op.item_id: + # Preserve existing topic_ids on REPLACE + lst[i] = Item(id=it.id, content=op.content, topic_ids=it.topic_ids) + replaced = True + break + if replaced: + break + if not replaced: + logger.warning( + "REPLACE target %r not found in context memory; skipping", + op.item_id, + ) + + elif op.type == OpType.ADD and op.section and op.content: + prefix = _SECTION_PREFIX.get(op.section, op.section[:2]) + new_id = f"{prefix}-{uuid.uuid4().hex}" + new_item = Item(id=new_id, content=op.content, topic_ids=topic_ids or []) + cm.section(op.section).append(new_item) + new_ids.append(new_id) + + return cm, new_ids + + def without(self, ids: set[str]) -> ContextMemory: + """Return a new ContextMemory without the specified items.""" + cm = self.model_copy(deep=True) + for sec in cm.section_names(): + lst = cm.section(sec) + lst[:] = [it for it in lst if it.id not in ids] + return cm + + def to_json(self) -> str: + """Serialize the memory to a JSON string.""" + return self.model_dump_json(indent=2) + + @classmethod + def from_json(cls, text: str) -> ContextMemory: + """Deserialize a memory from a JSON string.""" + return cls.model_validate_json(text) + + @classmethod + def merge(cls, *memories: ContextMemory) -> ContextMemory: + """Merge multiple context memories into a single memory. + + Later memories win on ID collision (items with duplicate IDs are + replaced by those from later memories in the argument list). + + Topics are deduplicated by ID, with later non-empty descriptions winning. + + Args: + *memories: One or more ContextMemory instances to merge. + + Returns: + A new ContextMemory containing merged topics and items. + """ + merged = cls() + + # Merge topics (deduplicate by id, later non-empty description wins) + topic_map: dict[str, Topic] = {} + for mem in memories: + for topic in mem.topics: + if topic.id not in topic_map: + topic_map[topic.id] = topic + elif topic.description and not topic_map[topic.id].description: + # Later non-empty description wins + topic_map[topic.id] = topic + merged.topics = list(topic_map.values()) + + # Merge items + for mem in memories: + for sec in cls.section_names(): + merged_section = merged.section(sec) + existing_ids = {item.id for item in merged_section} + for item in mem.section(sec): + if item.id in existing_ids: + # Replace existing item (later wins) + merged_section[:] = [ + it if it.id != item.id else item.model_copy(deep=True) + for it in merged_section + ] + else: + merged_section.append(item.model_copy(deep=True)) + existing_ids.add(item.id) + + return merged + + +def make_topic_id(repo_full_name: str, subroot: str, package_name: str | None = None) -> str: + """Build topic ID from repo identity and scope info. + + Priority: + 1. package_name provided and contains owner/repo -> use from owner/repo onwards + 2. package_name provided -> {repo_full_name}/{package_name} + 3. Fallback -> {repo_full_name}/{subroot} (or just repo_full_name for root) + + Args: + repo_full_name: Repository full name (owner/repo) + subroot: Path relative to repo root + package_name: Optional package name from manifest + + Returns: + Topic ID string + """ + if package_name: + if repo_full_name in package_name: + idx = package_name.index(repo_full_name) + return package_name[idx:] + return f"{repo_full_name}/{package_name}" + if subroot in (".", ""): + return repo_full_name + return f"{repo_full_name}/{subroot.strip('/')}" + + +def compute_common_ancestor_topic_id(repo_full_name: str, subroots: list[str]) -> str | None: + """Return topic ID for deepest common ancestor when >1 scope exists. + + Args: + repo_full_name: Repository full name (owner/repo) + subroots: List of subroot paths + + Returns: + Topic ID for common ancestor, or None if <=1 scope + """ + if len(subroots) <= 1: + return None + paths = [s.rstrip("/") for s in subroots if s not in (".", "")] + if len(paths) < 2: + return make_topic_id(repo_full_name, ".") + common = os.path.commonpath(paths) + if not common or common == ".": + return make_topic_id(repo_full_name, ".") + return make_topic_id(repo_full_name, common) + + + diff --git a/src/codespy/agents/memory/hippocampus/episode.py b/src/codespy/agents/memory/hippocampus/episode.py new file mode 100644 index 0000000..9eb9f8a --- /dev/null +++ b/src/codespy/agents/memory/hippocampus/episode.py @@ -0,0 +1,190 @@ +"""Episode record and persistence helpers for Hippocampus.""" + +from __future__ import annotations + +from datetime import UTC, datetime + +from pydantic import BaseModel, Field + +from codespy.agents.memory.hippocampus.context_memory import ContextMemory, Mutation +from codespy.tools.storage.base import Storage +from codespy.tools.storage.models import Entry, EntryType + + +class Episode(BaseModel): + """A snapshot of an agent's consolidated memory at the end of an episode. + + Recorded by ``Hippocampus.end_episode()`` after the buffered trajectories + have been distilled into the context memory. It captures *what the agent knew* + (the consolidated ``ContextMemory``) together with lightweight identity and + timing metadata, so a review/run leaves behind a durable, inspectable + record of the memory it produced. + + Attributes: + task: Name of the wrapped agent's top-level signature (e.g. + ``"CodeReviewSignature"``). Falls back to the module class name when + the wrapped module exposes no signature. + module: Class name of the wrapped ``dspy.Module`` (e.g. ``"CodeReviewer"``). + question: Question/task description derived from the first buffered + call's inputs (via ``question_field`` or serialized input fields). + context_memory: Deep-copied snapshot of the context memory *after* + consolidation, so later edits to the live memory do not mutate this + record. + timestamp: UTC time the episode was recorded. + artifacts: Named output artifacts produced by the wrapped agent for + this episode (e.g. ``{"review": ""}``). Agent-agnostic — + any caller can attach whatever markdown/text output it + produced under a key of its choosing. Empty by default. + run_id: Identifier of the pipeline run that produced this episode. + Shared by every agent/module invoked within the same + ``ReviewPipeline.forward()`` call, so all episodes from one + review run can be correlated. Also used as the ```` prefix + in the episode filename: ``--.json``. + mutations: Ordered sequence of Cartographer mutations applied during this episode. + """ + run_id: str = Field( + default="", + description=( + "Identifier of the pipeline run that produced this episode. " + "Shared across all agents invoked within the same review run." + ), + ) + timestamp: datetime = Field( + default_factory=lambda: datetime.now(UTC), + description="UTC time the episode was recorded", + ) + task: str = Field(description="Wrapped signature name (or module class name as fallback)") + module: str = Field(description="Wrapped dspy.Module class name") + question: str = Field(description="Question/task description for this episode (passed as 'question' or derived from serialized inputs)") + artifacts: dict[str, str] = Field( + default_factory=dict, + description="Named output artifacts produced by the agent (e.g. {'review': ''})", + ) + context_memory: ContextMemory = Field(description="Consolidated context memory snapshot") + mutations: list[Mutation] = Field( + default_factory=list, + description="Ordered sequence of Cartographer mutations applied during this episode", + ) + + +def save_episode(store: Storage, path: str, episode: Episode) -> None: + """Serialise ``episode`` to JSON and write it to ``path`` via ``store``. + + Args: + store: A ``FileSystem`` or ``S3Client`` instance. + path: Destination path (relative to the store's root / bucket). + episode: The episode to persist. + + Raises: + OSError: If the write operation fails. + """ + result = store.write_file( + path, episode.model_dump_json(indent=2), content_type="application/json" + ) + if not result.success: + raise OSError(f"Failed to save episode to {path!r}: {result.error}") + + +def load_episode(store: Storage, path: str) -> Episode: + """Load an episode from ``path`` via ``store``. + + Args: + store: A ``FileSystem`` or ``S3Client`` instance. + path: Source path (relative to the store's root / bucket). + + Returns: + The loaded ``Episode``. + + Raises: + FileNotFoundError: If the path does not exist in the store. + OSError: If reading or parsing fails. + """ + result = store.read_file(path) + if not result.success: + error = result.error or "" + if "not found" in error.lower() or "NoSuchKey" in error: + raise FileNotFoundError(f"Episode not found at {path!r}: {error}") + raise OSError(f"Failed to load episode from {path!r}: {error}") + if not result.content: + raise OSError(f"Episode at {path!r} is empty") + try: + episode = Episode.model_validate_json(result.content) + except Exception as exc: + raise OSError(f"Failed to parse episode from {path!r}: {exc}") from exc + return episode + + +def find_latest_episode( + store: Storage, + dir: str, + task: str | None = None, + exclude_task: str | None = None, + exclude_run_id: str | None = None, +) -> Episode | None: + """Find and load the most recent episode for a given scope path. + + Searches ``orgs/{owner}/episodic/.codespy/`` for episodes whose filename + starts with the slug derived from ``dir`` (same logic as + ``Hippocampus.episode_file_path``). Optionally filters by task name and + excludes a specific run_id. + + Args: + store: Storage backend (FileSystem or S3Client). + dir: Scope directory path (e.g., "/{repo_slug}/{subroot}/"). + Host segments (containing a dot) are stripped automatically. + task: Optional task filter (e.g., "scope", "summary"). + Matches ``-{task}-`` substring in filename remainder. + If None, any task matches. + exclude_task: Optional task to exclude (e.g., "scope"). + Episodes containing ``-{exclude_task}-`` in filename are skipped. + exclude_run_id: If set, skip episodes containing this run_id in + filename (avoids loading current pipeline's own episodes). + + Returns: + The most recent Episode by modified_at, or None if no matches found. + """ + # Compute slug and episodic directory (mirrors Hippocampus.episode_file_path) + segments = [s for s in dir.strip("/").split("/") if s] + if segments and "." in segments[0]: + segments = segments[1:] + if not segments: + return None + owner = segments[0] + slug = ".".join(segments) + episodic_dir = f"orgs/{owner}/episodic/.codespy" + + try: + listing = store.list_directory(episodic_dir) + except (FileNotFoundError, OSError): + return None + # Filter entries: prefix match + optional task + exclude run_id + # Filename: {slug}.{run_id}-{task}-{index}.json + prefix = f"{slug}." + candidates: list[Entry] = [] + for entry in listing.entries: + if entry.entry_type != EntryType.FILE: + continue + if not entry.name.startswith(prefix): + continue + remainder = entry.name[len(prefix):] + if task is not None and f"-{task}-" not in remainder: + continue + if exclude_task is not None and f"-{exclude_task}-" in remainder: + continue + if exclude_run_id and exclude_run_id in remainder: + continue + candidates.append(entry) + if not candidates: + return None + # Sort by modified_at descending; epoch fallback for entries without timestamp + _epoch = datetime.min.replace(tzinfo=UTC) + candidates.sort( + key=lambda e: e.modified_at if e.modified_at is not None else _epoch, + reverse=True, + ) + # Load the newest candidate + path = f"{episodic_dir}/{candidates[0].name}" + try: + return load_episode(store, path) + except (FileNotFoundError, OSError): + return None diff --git a/src/codespy/agents/memory/hippocampus/hippocampus.py b/src/codespy/agents/memory/hippocampus/hippocampus.py new file mode 100644 index 0000000..bfdc4a9 --- /dev/null +++ b/src/codespy/agents/memory/hippocampus/hippocampus.py @@ -0,0 +1,606 @@ +from __future__ import annotations + +import asyncio +import copy +import logging +import uuid +from datetime import UTC, datetime + +import dspy + +logger = logging.getLogger(__name__) + +from codespy.agents.memory.hippocampus.budget import ( + MemoryBudget, + _head_tail_text, + count_tokens, + evict, + format_inputs, + format_trajectory, +) +from codespy.agents.memory.hippocampus.context_memory import ( + ContextMemory, + ItemTag, + Mutation, + Operation, + OpType, +) +from codespy.agents.memory.hippocampus.episode import Episode +from codespy.agents.memory.hippocampus.episode import load_episode as _load_episode +from codespy.agents.memory.hippocampus.episode import save_episode as _save_episode +from codespy.agents.memory.hippocampus.modules.cartographer import Cartographer +from codespy.agents.memory.hippocampus.modules.distiller import Distiller +from codespy.tools.storage.base import Storage + + +def prepend_context_memory(sig): + """Prepend context_memory field to signature. + + The context_memory is passed as a pre-rendered string to avoid + Pydantic serialization in the LLM prompt. + """ + return sig.prepend( + name="context_memory", + field=dspy.InputField( + desc="Current context memory (topic-grouped, with item IDs and sections). Use it before redundant tool calls." + ), + type_=str, + ) + + +class Hippocampus(dspy.Module): + """Wraps a dspy.Module with a context memory that evolves via LLM-driven reflection. + + The context memory is prepended to every agent call so the agent starts each run + with accumulated orientation knowledge (structure, entities, constants) about + the external context. After calls, the Distiller extracts transferable + understanding and the Cartographer edits the memory — "caching understanding, + not answers." + + ## Two independent controls + + Reflection behaviour is governed by two orthogonal knobs: + + 1. **``max_reflects``** — maximum number of forward() calls that also reflect *online* + (in-episode warm-up). ``None`` (default) = no limit, reflect after every call. + ``0`` = never reflect online. ``N`` = reflect for the first N calls, buffer-only + thereafter. + + 2. **Calling ``end_episode()``** (or not) — whether to consolidate the buffered + episode into the memory at the end. Every call is *always* buffered so + ``end_episode()`` is available regardless of the online setting. + + Common patterns:: + + # Classic per-call (default) — reflect after every call, no end consolidation + mem = Hippocampus(agent) + pred = mem(task="…") + + # Pure batch — no online reflection, one holistic pass at the end + mem = Hippocampus(agent, max_reflects=0) + for task in tasks: + pred = mem(task=task) + mem.end_episode() + + # Hybrid — warm up online for the first 3 calls, then holistic consolidation + mem = Hippocampus(agent, max_reflects=3) + for task in tasks: + pred = mem(task=task) + mem.end_episode() + + # Read-only (memory never changes) — pure inference + mem = Hippocampus(agent, max_reflects=0) + pred = mem(task="…") # no end_episode() call + + # Async variants (for callers running inside an event loop, e.g. + # reviewer modules using `await agent.acall(...)`) + mem = Hippocampus(agent, max_reflects=0) + pred = await mem.acall(task="…") + await mem.aend_episode(store, dir) + + ## Trajectory bounding (two-stage) + + When ``budget.max_trajectory_tokens`` is set: + + - **Stage 1** (per call) — each trajectory is head+tail bounded at ``format_trajectory`` + time. This keeps the buffer lightweight. + - **Stage 2** (``end_episode``) — the joined episode is head+tail bounded again, so + the combined result is guaranteed to fit the budget even if many calls are buffered. + + With ``budget.max_trajectory_tokens=None`` both stages are no-ops and the + Distiller receives the full trajectory. + + ## Token budgets + + The four token budgets are grouped into :class:`MemoryBudget`; see that class + for what each one bounds and how to tune it. + """ + + def __init__( + self, + module: dspy.Module, + budget: MemoryBudget | None = None, + max_reflects: int | None = None, + question: str | None = None, + task_name: str | None = None, + run_id: str | None = None, + initial_memory: ContextMemory | None = None, + topic_ids: list[str] | None = None, + ): + """ + Args: + module: Any dspy.Module (ReAct, RLM, Predict, …) to wrap. + budget: The four token budgets bounding memory, as a + :class:`MemoryBudget`. Defaults to ``MemoryBudget()`` — see that + class for per-field guidance. Resolve one from configuration with + ``Settings.get_memory_budget(signature_name)``. + max_reflects: Maximum number of forward() calls that also reflect online. + None (default): no limit — reflect after every call (classic online learning). + 0: never reflect online — pure buffering until end_episode(). + N: reflect online for the first N calls, buffer-only afterwards. + Every call is always buffered regardless of this setting, so + end_episode() is always available. + question: Pre-computed question string for the reflection "question". + If set, this string is used directly as the Distiller question. + If None, all input fields are serialized (bounded by + ``budget.max_question_tokens``). + Set this when one field cleanly captures user intent. + task_name: Identity recorded in ``Episode.task`` and used in the episode + filename. Pass the signature's snake_case name (``"doc"``, + ``"code_review"``, …) — the same key that drives config, LM + selection and cost attribution — so the episode path lines up with + the rest of the system. Inference is a last resort: only + ``dspy.ReAct``-style modules expose ``.signature``, + ``dspy.ChainOfThought`` does not, so the fallback would yield a + meaningless (and collision-prone) ``"ChainOfThought"``. + run_id: Identifier of the pipeline run this agent belongs to. Passed + down by the orchestrating ``ReviewPipeline`` so every module + invoked within the same review run shares the same identifier, + used as the ```` prefix in the episode filename + (``-.json``) and recorded on ``Episode.run_id``. + If ``None`` (standalone usage), a random UUID is generated. + initial_memory: Optional context memory to seed the agent with. When + provided, the agent starts with this memory instead of an empty one, + inheriting accumulated understanding from upstream pipeline stages. + topic_ids: Optional list of topic IDs to auto-assign to all new items + created during this episode. Used for scope-aware memory organization. + """ + super().__init__() + + module = copy.deepcopy(module) + + # Prepend context_memory only to predictors that receive the module's own + # input fields. + top_sig = getattr(module, "signature", None) + if top_sig is not None: + module_inputs = set(top_sig.input_fields) + module.signature = prepend_context_memory(top_sig) + for _, pred in module.named_predictors(): + if set(pred.signature.input_fields) & module_inputs: + if "context_memory" not in pred.signature.input_fields: + pred.signature = prepend_context_memory(pred.signature) + else: + for _, pred in module.named_predictors(): + pred.signature = prepend_context_memory(pred.signature) + + self.agent = module + self.distill = Distiller() + self.cartograph = Cartographer() + self.budget = budget or MemoryBudget() + self.max_reflects = max_reflects + self.question = question + self.cmem = initial_memory.model_copy(deep=True) if initial_memory else ContextMemory() + self._topic_ids = topic_ids or [] + self.scores: dict[str, int] = {} + # Buffer of per-call bounded trajectory strings, cleared after end_episode(). + self._episode_trajectories: list[str] = [] + # Count of buffered trajectories already distilled online (via max_reflects). + # Used to skip a redundant consolidation distill in the single-call case, + # and to detect "everything was reflected online" so end_episode() still + # persists a snapshot even when nothing remains to consolidate. + self._reflected_count: int = 0 + # Question derived from the latest buffered call; used as Distiller consolidation input. + self._episode_question: str | None = None + + # Identity of the wrapped module/signature for Episode metadata. An explicit + # task_name wins: inference only works for modules exposing .signature. + self._task_name: str + if task_name: + self._task_name = task_name + elif top_sig is not None: + self._task_name = top_sig.__name__ + else: + fallback = type(module).__name__ + logger.warning( + "Hippocampus: task_name not provided and module %r has no .signature; " + "using collision-prone fallback %r. Pass task_name explicitly.", + module, fallback, + ) + self._task_name = fallback + self._module_name: str = type(module).__name__ + # Identifier of the pipeline run this agent belongs to (see run_id arg + # above). Falls back to a random UUID for standalone usage where no + # orchestrator provides one. + self._run_id: str = run_id or uuid.uuid4().hex + # Counter for episode filenames to avoid collisions when the same + # signature is invoked multiple times on the same scope within one run. + self._episode_index: int = 0 + # The most recent consolidated Episode; set by end_episode(), None until then. + self.episode: Episode | None = None + # Accumulated mutations across _distill() calls within the current episode. + self._mutations: list[Mutation] = [] + # Step counter incremented per _distill() call for mutation grouping. + self._distill_step: int = 0 + + @property + def current_memory_text(self) -> str: + """Return the rendered context memory as text.""" + return self.cmem.render() + + def forward(self, **kwargs) -> dspy.Prediction: + pred = self.agent(context_memory=self.cmem.render(), **kwargs) + self._buffer_and_distill(pred, kwargs) + return pred + + async def aforward(self, **kwargs) -> dspy.Prediction: + """Async counterpart of :meth:`forward`. + + Awaits the wrapped agent's ``acall`` instead of invoking it + synchronously — required when the caller is already inside a running + event loop (e.g. reviewer modules using ``await agent.acall(...)``). + The Distiller/Cartographer reflection pass is still synchronous under + the hood but is offloaded to a thread so it never blocks the loop. + """ + pred = await self.agent.acall(context_memory=self.cmem.render(), **kwargs) + await asyncio.to_thread(self._buffer_and_distill, pred, kwargs) + return pred + + def _buffer_and_distill(self, pred: dspy.Prediction, kwargs: dict) -> None: + """Shared post-call work for both ``forward`` and ``aforward``. + + Buffers the (stage-1 bounded) trajectory and, depending on + ``max_reflects``, runs an online distill+apply pass immediately. + """ + traj = format_trajectory(pred, self.budget.max_trajectory_tokens) + self._episode_trajectories.append(traj) + self._episode_question = self._make_question(kwargs) + # Online reflection: None = no limit (always); N = for the first N calls. + if (self.max_reflects is None + or len(self._episode_trajectories) <= self.max_reflects): + try: + self._distill(traj, self._episode_question) + self._reflected_count += 1 + except Exception: + logger.warning( + "Online reflection failed for %s; trajectory buffered for end_episode().", + self._task_name, + exc_info=True, + ) + + def _consolidate(self) -> str | None: + """Join buffered trajectories (stage-2 bounded) and distill+apply once. + + Returns the combined trajectory text used for consolidation, or + ``None`` if the buffer is empty (no-op). + """ + skip_double_distill = len(self._episode_trajectories) == 1 and self._reflected_count > 0 + if not self._episode_trajectories or skip_double_distill: + return None + combined = "\n\n".join( + f"=== Call {i + 1} ===\n{t}" + for i, t in enumerate(self._episode_trajectories) + ) + if self.budget.max_trajectory_tokens is not None: + combined = _head_tail_text(combined, self.budget.max_trajectory_tokens) + try: + self._distill(combined, self._episode_question or "") + except Exception: + logger.warning( + "Consolidation reflection failed for %s; episode saved without final distill.", + self._task_name, + exc_info=True, + ) + return combined + + def _finalize_episode(self, artifacts: dict[str, str] | None = None) -> None: + """Record the consolidated Episode snapshot and clear the buffer. + + Args: + artifacts: Named output artifacts to attach to the recorded + episode (e.g. ``{"review": ""}``). Defaults to an + empty dict when omitted. + """ + self.episode = Episode( + task=self._task_name, + module=self._module_name, + question=self._episode_question or "", + context_memory=self.cmem.model_copy(deep=True), + timestamp=datetime.now(UTC), + artifacts=artifacts or {}, + run_id=self._run_id, + mutations=self._mutations, + ) + self._episode_trajectories.clear() + self._episode_question = None + self._reflected_count = 0 + self._mutations.clear() + self._distill_step = 0 + + + def episode_file_path(self, dir: str, index: int = 0) -> str: + """Build the full episode file path from a directory. + + Path format: ``orgs//episodic/.codespy/.[.].--.json`` + + Args: + dir: Directory identifying where this episode belongs (e.g. a + scope's ``/{host}/{owner}/{repo}/{subroot}/`` path or + ``/{owner}/{repo}/{subroot}/`` without host). + index: Episode index for this scope/task combination. + """ + segments = [s for s in dir.strip("/").split("/") if s] + # Strip host segment (contains a dot, e.g. github.com/gitlab.com) + if segments and "." in segments[0]: + segments = segments[1:] + owner = segments[0] if segments else "unknown" + slug = ".".join(segments) + return f"orgs/{owner}/episodic/.codespy/{slug}.{self._run_id}-{self._task_name}-{index}.json" + + def end_episode( + self, + store: Storage | None = None, + dir: str | None = None, + artifacts: dict[str, str] | None = None, + ) -> None: + """Consolidate the buffered trajectories into the memory and record an Episode snapshot. + + A single Distiller pass sees all buffered trajectories joined with + ``=== Call k ===`` headers. If ``budget.max_trajectory_tokens`` is set, the + combined text is head+tail bounded (stage 2) after per-call bounding + (stage 1) already applied at append time. The question is derived from + the first buffered call. No-op if the buffer is empty. + + After consolidation ``self.episode`` is set to a new :class:`Episode` + containing the task/module identity and a deep-copy snapshot of the + updated context memory. + + If both ``store`` and ``dir`` are provided the episode is persisted + via ``save_episode()`` after consolidation, at + ``orgs//episodic/.codespy/.--.json``. ``store`` may be a + ``FileSystem`` or an ``S3Client`` instance. + + Args: + store: Optional ``Storage`` backend to persist the episode after + consolidation (``FileSystem`` or ``S3Client``). + dir: Directory identifying where this episode belongs (e.g. a + scope's path). Required when ``store`` is set. + artifacts: Named output artifacts to attach to the recorded + episode (e.g. ``{"review": ""}``). Agent-agnostic — + any caller can attach whatever markdown/text output it + produced under a key of its choosing. + + Raises: + OSError: If persistence is requested and the write fails. + """ + combined = self._consolidate() + has_content = combined is not None or self._reflected_count > 0 + if not has_content: + return + self._finalize_episode(artifacts) + if store is not None and dir is not None: + _save_episode(store, self.episode_file_path(dir, self._episode_index), self.episode) + self._episode_index += 1 + + async def aend_episode( + self, + store: Storage | None = None, + dir: str | None = None, + artifacts: dict[str, str] | None = None, + ) -> None: + """Async counterpart of :meth:`end_episode`. + + The (synchronous) Distiller/Cartographer consolidation pass and the + storage write are both offloaded to a thread so they never block the + caller's event loop. + + Args: + store: Optional ``Storage`` backend to persist the episode after + consolidation (``FileSystem`` or ``S3Client``). + dir: Directory identifying where this episode belongs (e.g. a + scope's path). Required when ``store`` is set. + artifacts: Named output artifacts to attach to the recorded + episode (e.g. ``{"review": ""}``). + """ + combined = await asyncio.to_thread(self._consolidate) + has_content = combined is not None or self._reflected_count > 0 + if not has_content: + return + await asyncio.to_thread(self._finalize_episode, artifacts) + if store is not None and dir is not None: + path = self.episode_file_path(dir, self._episode_index) + await asyncio.to_thread(_save_episode, store, path, self.episode) + self._episode_index += 1 + + + def save_episode(self, store: Storage, path: str) -> None: + """Persist the current episode to ``path`` via ``store``. + + Args: + store: Storage backend (``FileSystem`` or ``S3Client``). + path: Destination path within the store. + + Raises: + ValueError: If no episode has been consolidated yet (call + ``end_episode()`` first). + OSError: If the write fails. + """ + if self.episode is None: + raise ValueError( + "No episode to save — call end_episode() to consolidate first." + ) + _save_episode(store, path, self.episode) + + def load_episode(self, store: Storage, path: str) -> None: + """Replace the current state with an episode loaded from ``path`` via ``store``. + + Restores both ``self.episode`` and the live context memory + (``self.cmem = episode.context_memory``) so the agent resumes from the + persisted state. Also resets ``scores`` and clears the trajectory + buffer since they belong to the previous state. + + Args: + store: Storage backend (``FileSystem`` or ``S3Client``). + path: Source path within the store. + + Raises: + FileNotFoundError: If the path does not exist. + OSError: If reading or parsing fails. + """ + ep = _load_episode(store, path) + self.episode = ep + self.cmem = ep.context_memory + self.scores = {} + self._episode_trajectories.clear() + self._episode_question = None + self._reflected_count = 0 + self._episode_index = 0 + self._mutations.clear() + self._distill_step = 0 + + def reset_episode(self) -> None: + """Discard the buffered trajectories without reflecting.""" + self._episode_trajectories.clear() + self._episode_question = None + self._reflected_count = 0 + self._episode_index = 0 + self._mutations.clear() + self._distill_step = 0 + + # ------------------------------------------------------------------ + # Internals + # ------------------------------------------------------------------ + + def _make_question(self, inputs: dict) -> str: + if self.question is not None: + return self.question + return format_inputs(inputs, self.budget.max_question_tokens) + + def _record_mutations( + self, ops: list[Operation], new_ids: list[str], pre_memory: ContextMemory + ) -> list[Mutation]: + """Build Mutation records from operations and the new IDs generated by apply(). + + For DELETE/REPLACE, looks up pre-mutation state (section and previous_content). + For ADD, back-fills item_ids from new_ids in order. + + Args: + ops: Cartographer operations (ADD/DELETE/REPLACE). + new_ids: IDs of items created by apply() in the same order as ADD ops. + pre_memory: Context memory state before apply() — used to look up + previous content for DELETE/REPLACE. + + Returns: + List of Mutation records for this step. + """ + mutations: list[Mutation] = [] + add_mutations: list[Mutation] = [] + for op in ops: + if op.type == OpType.DELETE and op.item_id: + found = pre_memory.find_item(op.item_id) + if found: + section, old_item = found + mutations.append( + Mutation( + step=self._distill_step, + type=OpType.DELETE, + item_id=op.item_id, + section=section, + content=None, + previous_content=old_item.content, + topic_ids=old_item.topic_ids, + ) + ) + elif op.type == OpType.REPLACE and op.item_id and op.content: + found = pre_memory.find_item(op.item_id) + if found: + section, old_item = found + mutations.append( + Mutation( + step=self._distill_step, + type=OpType.REPLACE, + item_id=op.item_id, + section=section, + content=op.content, + previous_content=old_item.content, + topic_ids=old_item.topic_ids, + ) + ) + elif op.type == OpType.ADD and op.section and op.content: + mut = Mutation( + step=self._distill_step, + type=OpType.ADD, + item_id="", + section=op.section, + content=op.content, + previous_content=None, + topic_ids=list(self._topic_ids), + ) + mutations.append(mut) + add_mutations.append(mut) + # Back-fill ADD mutation item_ids from new_ids + for mut, new_id in zip(add_mutations, new_ids, strict=True): + mut.item_id = new_id + return mutations + + def _update_item_scores(self, tags: dict[str, ItemTag]) -> None: + """Adjust item scores based on Distiller-assigned tags. + + HELPFUL: +1, HARMFUL/STALE: -1, NEUTRAL: ensure entry exists (default 0). + """ + for bid, tag in tags.items(): + if tag == ItemTag.HELPFUL: + self.scores[bid] = self.scores.get(bid, 0) + 1 + elif tag in (ItemTag.HARMFUL, ItemTag.STALE): + self.scores[bid] = self.scores.get(bid, 0) - 1 + else: + self.scores.setdefault(bid, 0) + + def _distill(self, trajectory: str, question: str) -> None: + distilled = self.distill( + trajectory=trajectory, + context_memory=self.cmem.render(), + question=question, + max_context_item_tokens=self.budget.max_context_item_tokens, + ) + + known = self.cmem.ids() + tags = {k: v for k, v in (distilled.item_tags or {}).items() if k in known} + self._update_item_scores(tags) + + edits = self.cartograph( + diagnosis=distilled.diagnosis, + item_tags=tags, + cache_candidates=list(distilled.cache_candidates or []), + current_map=self.cmem.render(), + question=question, + # The Cartographer's input field keeps the generic name: it is prompt + # text, already scoped by its description, and pairs with current_tokens. + token_budget=self.budget.max_context_memory_tokens, + current_tokens=count_tokens(self.cmem.render()), + max_context_item_tokens=self.budget.max_context_item_tokens, + ) + ops = list(edits.operations or []) + + if ops: + pre_memory = self.cmem + self.cmem, new_ids = self.cmem.apply(ops, topic_ids=self._topic_ids) + mutations = self._record_mutations(ops, new_ids, pre_memory) + self._mutations.extend(mutations) + for nid in new_ids: + self.scores[nid] = self.scores.get(nid, 0) + 1 + + self._distill_step += 1 + self.cmem = evict(self.cmem, self.scores, self.budget.max_context_memory_tokens) + + live = self.cmem.ids() + self.scores = {k: v for k, v in self.scores.items() if k in live} diff --git a/src/codespy/agents/memory/hippocampus/modules/__init__.py b/src/codespy/agents/memory/hippocampus/modules/__init__.py new file mode 100644 index 0000000..1099e60 --- /dev/null +++ b/src/codespy/agents/memory/hippocampus/modules/__init__.py @@ -0,0 +1,13 @@ +"""DSPy modules for the hippocampus agent.""" + +from codespy.agents.memory.hippocampus.context_memory import Mutation +from codespy.agents.memory.hippocampus.modules.cartographer import Cartographer, CartographerSig +from codespy.agents.memory.hippocampus.modules.distiller import Distiller, DistillerSig + +__all__ = [ + "Cartographer", + "CartographerSig", + "Distiller", + "DistillerSig", + "Mutation", +] diff --git a/src/codespy/agents/memory/hippocampus/modules/cartographer.py b/src/codespy/agents/memory/hippocampus/modules/cartographer.py new file mode 100644 index 0000000..8a310c2 --- /dev/null +++ b/src/codespy/agents/memory/hippocampus/modules/cartographer.py @@ -0,0 +1,162 @@ +from __future__ import annotations + +import dspy + +from codespy.agents.context_safe import ContextSafe +from codespy.agents.memory.hippocampus.context_memory import ( + CacheCandidate, + ItemTag, + Operation, +) + + +class CartographerSig(dspy.Signature): + """You are a context memory curator. You maintain a concise, high-value + context memory prepended to an agent that repeatedly interacts with a long + external context. + + The context memory captures the agent's evolving UNDERSTANDING of the + context — NOT answers to specific questions. Think of it as the mental + model a human builds after reading a document: structure, key entities, + relationships, and global summaries that help with ANY question about + the content. + + ## Instructions + + - Review the latest Distiller diagnosis and the current context memory. + - Prioritize items representing SHARED UNDERSTANDING — knowledge + useful across many different questions on this context. + - Demote or remove question-specific facts that only help one query. + - Keep items that are structural, relational, or globally informative. + - Remove items that are stale, misleading, redundant, or low-value. + - Rewrite items when a more compact or more useful version exists. + Prefer REPLACE over ADD when possible. + - Add new items only when they represent transferable understanding. + - Each item must be short and budget-efficient — stay within the + `max_context_item_tokens` budget given as an input. If a candidate exceeds + it, rewrite it more compactly or split it. + - If nothing new is worth keeping, return an empty operations list. + + The litmus test: For each item, ask "Would a future agent asking a + completely DIFFERENT question about this context benefit from knowing + this?" If not, it probably isn't worth the budget. + + ## How to use item_tags + + The Distiller assigns each existing item a tag. Let it drive your ops: + - harmful / stale → DELETE the item (unless a corrected REPLACE is + clearly the better fix). + - helpful but verbose or redundant → REPLACE with a tighter version. + - helpful and already compact → leave it; don't spend an op. + - neutral → keep as-is; do not churn ops on neutral items. + + ## Operation rules + + Emit only well-formed operations that satisfy the schema: + - ADD: requires `section` (one of the five section names) and `content`. + - DELETE: requires `item_id`. + - REPLACE: requires `item_id` and `content`. + - Only reference `item_id`s that exist in the current memory. Never invent + ids — new items get their ids assigned automatically on ADD. + + ## Value Priority (highest to lowest) + + 1. context_understanding — entity/concept inventories (key actors, + data categories, their roles/relationships), global summaries, + and any structural knowledge that orients the agent for arbitrary + questions + 2. domain_constants — exact numeric values the context defines for + computation: thresholds, rates, formulas, conversion factors, + reference ranges, enum sets, required output field names/types. + These must remain numerically precise — do not abstract them. + 3. context_roadmap — section/chapter/document index with topics and + approximate locations — a Table of Contents the agent won't have + to rebuild + 4. reusable_results — agent-derived aggregated outputs (counts, + distributions, classifications) from processing the full context + that multiple questions would need. Note the computation method + to judge reliability. + 5. parsing_schema — format observations, delimiters, splitting + methods — cheap to rediscover but saves one iteration + + ## Do NOT add + + - Facts that answer only one specific question (verbatim quotes + resolving a single query) + - Raw data dumps or lengthy excerpts copied from the context — + abstract these into higher-level understanding + - Advisory rules, warnings, or meta-instructions ("always do X", + "never do Y") — these consume budget and are not reliably followed + - Verbose passages or long excerpts — prefer compact summaries + + ## Do NOT abstract away + + - Exact numeric values (thresholds, rates, formulas, conversion + factors) that the context defines for computation + - Reference values, enum sets, or allowed value lists + - Output field names, types, and structural requirements + - These are domain constants, not raw data — they must remain precise + + Token-budget enforcement is handled by a separate evictor; focus on + selection quality. But be mindful of the budget when proposing edits. + """ + + diagnosis: str = dspy.InputField(desc="Distiller's narrative diagnosis.") + item_tags: dict[str, ItemTag] = dspy.InputField(desc="Per-item tags from the Distiller.") + cache_candidates: list[CacheCandidate] = dspy.InputField( + desc="Candidate items the Distiller proposed." + ) + current_map: str = dspy.InputField(desc="Current context memory (topic-grouped, with item IDs and sections).") + question: str = dspy.InputField(desc="Question the agent was answering.") + token_budget: int = dspy.InputField(desc="Hard token budget for the context memory.") + current_tokens: int = dspy.InputField(desc="Current token count of the context memory.") + max_context_item_tokens: int = dspy.InputField( + desc="Token budget for a SINGLE context memory item. Every ADD/REPLACE content " + "must stay within it." + ) + + justification: str = dspy.OutputField( + desc="Brief explanation of why these edits improve the shared understanding " + "cached in the context memory." + ) + + operations: list[Operation] = dspy.OutputField( + desc="Ordered list of ADD/DELETE/REPLACE ops to apply. Empty if nothing " + "is worth changing." + ) + + +class Cartographer(dspy.Module): + """Translates the Distiller's structured reflection into concrete edits + against the context memory. + + Owns *what is worth keeping* — selects which tagged items to drop, which + candidates to add, and which existing items to rewrite. Token-budget + enforcement is the Evictor's job. + """ + + # Name this module's settings live under: memory.cartographer. + SIGNATURE = "cartographer" + + def __init__(self): + super().__init__() + self.predict = ContextSafe(dspy.ChainOfThought(CartographerSig), CartographerSig, name="cartographer") + + def forward(self, diagnosis, item_tags, cache_candidates, current_map, question, + token_budget, current_tokens, max_context_item_tokens): + # See Distiller.forward: SignatureContext applies memory.cartographer's + # LLM settings and gives this module its own cost line. Entered here + # because DSPy's context is thread-scoped and reflection runs in a worker. + from codespy.agents import SignatureContext, get_cost_tracker + + with SignatureContext(self.SIGNATURE, get_cost_tracker()): + return self.predict( + diagnosis=diagnosis, + item_tags=item_tags, + cache_candidates=cache_candidates, + current_map=current_map, + question=question, + token_budget=token_budget, + current_tokens=current_tokens, + max_context_item_tokens=max_context_item_tokens, + ) diff --git a/src/codespy/agents/memory/hippocampus/modules/distiller.py b/src/codespy/agents/memory/hippocampus/modules/distiller.py new file mode 100644 index 0000000..2db74a1 --- /dev/null +++ b/src/codespy/agents/memory/hippocampus/modules/distiller.py @@ -0,0 +1,176 @@ +from __future__ import annotations + +import dspy + +from codespy.agents.context_safe import ContextSafe +from codespy.agents.memory.hippocampus.context_memory import ( + CacheCandidate, + ItemTag, +) + + +class DistillerSig(dspy.Signature): + """You are an expert analyst reviewing an agent's execution trajectory + after it interacted with a long external context to answer a question. + + ## Key Principle: Cache Understanding, Not Answers + + The context memory prepended to the agent is a compact CACHE OF + UNDERSTANDING about the external context — NOT answers to specific + questions. Think of it as the mental model a human builds after reading + a document: structure, key entities, relationships, and global summaries + that help with ANY future question on the same context. + + ## Orientation vs. Question-Specific Work + + Observe what the agent spent its iterations doing. Work falls into two + categories: + 1. ORIENTATION WORK — figuring out what the context is, how it's + organized, what entities/concepts exist, how they relate. This + understanding transfers to ANY future question. + 2. QUESTION-SPECIFIC WORK — locating the specific passage or fact + needed for THIS question. This rarely helps other questions. + + Focus on caching category (1). Ask: "If a different, unrelated question + were asked about this same context, would this cached item save the + agent work?" + + ## Produce three outputs + + 1. DIAGNOSIS — Brief (3-5 sentences; it feeds the next module's prompt, + so keep it terse) analysis of: + - How many iterations the agent spent on orientation vs. + question-specific work + - Whether the agent re-discovered structural information that was + already available (or should have been cached) + - What kind of contextual understanding the agent built that + could transfer to future questions + + 2. ITEM_TAGS — For EVERY item currently in the context memory, tag it exactly: + - helpful: directly helped or would directly help this run + - harmful: misleading, incorrect, or actively hurts performance + - neutral: correct domain knowledge not relevant to THIS question + but plausibly useful for other questions + - stale: outdated, superseded, or no longer accurate + When tagging, distinguish between "not needed for this question" + (neutral) from "not useful for any question" (harmful/stale). + Domain constants, formulas, and output schemas not exercised this + run are typically NEUTRAL, not harmful. + + 3. CACHE_CANDIDATES — Items to ADD. Value tiers: + + Highest value — structural understanding that transfers across + questions: + - Context structure map: what sections/chapters/documents exist, + their topics, and approximate locations (like a Table of + Contents the agent won't have to rebuild) + - Entity/concept inventory: key characters, actors, concepts, or + data categories and their roles or relationships — a brief + "glossary" that orients the agent + - Domain constants: exact numeric values the context defines — + thresholds, rates, formulas, conversion factors, reference + ranges, enum sets, required output field names/types. Keep + these numerically precise. + - Global summaries: high-level understanding of what the context + is about — genre, time period, key themes, nature of the data + — that frames any question + + Medium value: + - Parsing schema: document delimiters, boundary patterns, field + format, how to reliably split or locate items in the context + - Shared intermediate computations: aggregated results (counts, + distributions, classifications) that the agent derived by + processing the full context and that multiple questions would + need. Note the computation method to judge reliability. + + Do NOT cache: + - Facts that answer only one specific question (e.g., a verbatim + quote that resolves a single query) + - Verbose passages or long excerpts — prefer compact summaries + - Advisory rules, warnings, or meta-instructions ("always do X", + "never do Y") — the cache is for understanding, not instructions + - Results from naive surface-level text operations (e.g., + str.count() for frequency estimation) + - Verbatim answers to the current question + + Do NOT abstract away exact numeric values, enum sets, output field + names/types — these are domain constants and must remain precise. + + Assign each candidate to one of these exact section names (they map + onto the context memory schema): context_understanding, domain_constants, + context_roadmap, reusable_results, parsing_schema. + + Each candidate is a JSON object with exactly these fields: + - section: one of the five section names above + - value: the compact cached content (stay within max_context_item_tokens) + - transferability: what kinds of future questions this would help + - rationale: why this is shared understanding, not a one-off fact + + Do NOT invent extra fields (no "id", no "content", no "name"). + + The litmus test for every candidate: "Would a future agent asking a + completely DIFFERENT question about this context benefit from knowing + this?" + """ + + trajectory: str = dspy.InputField(desc="The agent's full execution trajectory.") + context_memory: str = dspy.InputField(desc="Current context memory (topic-grouped, with item IDs).") + question: str = dspy.InputField(desc="The question the agent was answering.") + max_context_item_tokens: int = dspy.InputField( + desc="Token budget for a SINGLE context memory item. Keep every candidate within " + "it; if one exceeds it, rewrite it more compactly or split it." + ) + + diagnosis: str = dspy.OutputField( + desc="Brief (3-5 sentence) analysis of orientation vs. question-specific work, " + "whether structural info was re-discovered that should have been cached, and " + "what transferable understanding the agent built. Feeds the Cartographer prompt." + ) + item_tags: dict[str, ItemTag] = dspy.OutputField( + desc="Per-item-id tag for EVERY item currently in the context memory. " + "Keys must match existing item ids exactly." + ) + cache_candidates: list[CacheCandidate] = dspy.OutputField( + desc="Candidate items to add. Each within the max_context_item_tokens budget; " + "structural/transferable only. " + "Each candidate's `section` must be one of the five section names above." + ) + + +class Distiller(dspy.Module): + """Extracts transferable orientation knowledge from an agent trajectory. + + The context memory is a prompt-resident cache of *understanding*, not + answers. The Distiller separates orientation work (what the context + contains, how it's organized, which constants matter) from question- + specific work, tags every existing item, and proposes new candidates. + """ + + # Name this module's settings live under: memory.distiller. + SIGNATURE = "distiller" + + def __init__(self): + super().__init__() + self.predict = ContextSafe(dspy.ChainOfThought(DistillerSig), DistillerSig, name="distiller") + + def forward( + self, + trajectory: str, + context_memory: str, + question: str, + max_context_item_tokens: int, + ): + # SignatureContext applies memory.distiller's model/temperature/reasoning + # effort and attributes the cost to this module rather than to whichever + # agent triggered the reflection. It must be entered here, not by the + # caller: Hippocampus reflects inside an asyncio.to_thread worker and + # DSPy's context is thread-scoped. + from codespy.agents import SignatureContext, get_cost_tracker + + with SignatureContext(self.SIGNATURE, get_cost_tracker()): + return self.predict( + trajectory=trajectory, + context_memory=context_memory, + question=question, + max_context_item_tokens=max_context_item_tokens, + ) diff --git a/src/codespy/agents/reviewer/models.py b/src/codespy/agents/reviewer/models.py index 0ab37ed..67e9dad 100644 --- a/src/codespy/agents/reviewer/models.py +++ b/src/codespy/agents/reviewer/models.py @@ -1,13 +1,29 @@ """Data models for code review results.""" -from datetime import datetime -from enum import Enum +from datetime import UTC, datetime +from enum import StrEnum from pathlib import Path -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field +from codespy.agents.memory.hippocampus import ContextMemory -class IssueSeverity(str, Enum): + +class PRContext(BaseModel): + """Shared PR identity passed to all review modules after summarization. + + Built by the pipeline orchestrator after the Summarizer runs, then + threaded through scope identification, review modules, and audit. + Each module constructs its own Hippocampus question from these fields. + """ + + repo_slug: str = Field(description="Host-qualified repo identifier (e.g. github.com/owner/repo)") + pr_number: int = Field(description="PR number") + pr_title: str = Field(description="PR title") + summary: str = Field(description="2-3 sentence PR summary produced by Summarizer") + + +class IssueSeverity(StrEnum): """Severity level of an issue.""" CRITICAL = "critical" @@ -17,7 +33,7 @@ class IssueSeverity(str, Enum): INFO = "info" -class IssueCategory(str, Enum): +class IssueCategory(StrEnum): """Category of an issue.""" SECURITY = "security" @@ -26,7 +42,7 @@ class IssueCategory(str, Enum): SMELL = "smell" -class ScopeType(str, Enum): +class ScopeType(StrEnum): """Type of code scope in a repository.""" LIBRARY = "library" # Shared code that others import @@ -46,14 +62,44 @@ class PackageManifest(BaseModel): dependencies_changed: bool = Field( default=False, description="Whether PR modified this manifest or lock file" ) + package_name: str | None = Field(default=None, description="Package identity from manifest") + + +from codespy.tools.git.models import ChangedFile, PullRequest + + +class ReviewMetadata(BaseModel): + """Runtime pipeline state, stable once constructed at pipeline start. + + Groups repo_path, run_id, pr, and is_local to reduce parameter + proliferation across module method signatures. + """ + + repo_path: Path + run_id: str | None = None + pr: PullRequest | None = None + is_local: bool = False + + +class ReviewContext(BaseModel): + """Evolving pipeline state threaded through review stages. + Carries both the immutable PR identity and the inherited context memory + from upstream pipeline stages. Updated at each stage boundary + so downstream modules inherit accumulated understanding. + """ -from codespy.tools.git.models import ChangedFile + pr_context: PRContext = Field(description="Immutable PR identity (repo, number, title, summary)") + memory: ContextMemory | None = Field(default=None, description="Inherited context memory from upstream stages") + metadata: ReviewMetadata | None = Field(default=None, description="Runtime pipeline state") class ScopeResult(BaseModel): """A detected scope/subroot in the repository.""" + repo: str = Field( + default="", description="Repo identifier: 'owner/repo' (remote) or local dir name" + ) subroot: str = Field(description="Path relative to repo root (e.g., packages/auth)") scope_type: ScopeType = Field(description="Type of scope (library, service, etc.)") has_changes: bool = Field( @@ -62,9 +108,6 @@ class ScopeResult(BaseModel): is_dependency: bool = Field( default=False, description="Whether this scope depends on a changed scope" ) - confidence: float = Field( - default=0.8, ge=0.0, le=1.0, description="Confidence score for scope identification" - ) language: str | None = Field(default=None, description="Primary language detected") package_manifest: PackageManifest | None = Field( default=None, description="Package manifest info if present" @@ -73,9 +116,40 @@ class ScopeResult(BaseModel): default_factory=list, description="Changed files belonging to this scope" ) reason: str = Field(description="Explanation for why this scope was identified") + skills: str | None = Field( + default=None, description="Project/scope instructions inherited from ancestor directories" + ) + description: str = Field( + default="", description="Description of scope's role in the project (max 500 chars)" + ) model_config = {"arbitrary_types_allowed": True} + def scope_path(self) -> str: + """Return the storage-relative path for this scope. + + Used by Hippocampus memory as the base directory for episode files. + ``subroot == "."`` (repo root) results in ``/{repo}/``. + """ + if self.subroot in (".", ""): + return f"/{self.repo}/" + return f"/{self.repo}/{self.subroot.strip('/')}/" + + def topic(self, repo_full_name: str) -> "Topic": + """Build the Topic for this scope. + + Args: + repo_full_name: Repository full name (owner/repo) + + Returns: + Topic object with id and description + """ + from codespy.agents.memory.hippocampus.context_memory import Topic, make_topic_id + + package_name = self.package_manifest.package_name if self.package_manifest else None + topic_id = make_topic_id(repo_full_name, self.subroot, package_name) + return Topic(id=topic_id, description=self.description) + class Issue(BaseModel): """Represents a single issue found during review.""" @@ -108,7 +182,7 @@ def location(self) -> str: class SignatureStatsResult(BaseModel): """Statistics for a single signature's execution during review.""" - name: str = Field(description="Signature name (e.g., code_review, doc, scope, supply_chain, summarization)") + name: str = Field(description="Signature name (e.g., code_review, doc, scope, supply_chain, summary, audit)") cost: float = Field(default=0.0, description="Cost in USD for this signature") tokens: int = Field(default=0, description="Tokens used by this signature") call_count: int = Field(default=0, description="Number of LLM calls made by this signature") @@ -130,14 +204,20 @@ def tokens_per_call(self) -> float: class ReviewResult(BaseModel): - """Complete review results for a merge request (GitHub PR or GitLab MR).""" + """Complete review results for a pull request (GitHub PR or GitLab MR).""" - mr_number: int = Field(description="MR number") - mr_title: str = Field(description="MR title") - mr_url: str = Field(description="MR URL") + pr_number: int = Field(description="PR number") + pr_title: str = Field(description="PR title") + pr_url: str = Field(description="PR URL") repo: str = Field(description="Repository name (owner/repo)") + run_id: str = Field( + default="", + description="Identifier of the pipeline run that produced this result, " + "shared with all Episode records persisted during this run", + ) reviewed_at: datetime = Field( - default_factory=datetime.utcnow, description="Review timestamp" + default_factory=lambda: datetime.now(UTC), + description="Review timestamp", ) model_used: str = Field(description="LLM model used for review") issues: list[Issue] = Field( @@ -199,9 +279,9 @@ def issues_by_severity(self) -> dict[IssueSeverity, list[Issue]]: def to_markdown(self) -> str: """Format review results as Markdown.""" lines = [ - f"# Code Review: {self.mr_title}", + f"# Code Review: {self.pr_title}", "", - f"**MR:** [{self.repo}#{self.mr_number}]({self.mr_url})", + f"**PR:** [{self.repo}#{self.pr_number}]({self.pr_url})", f"**Reviewed at:** {self.reviewed_at.strftime('%Y-%m-%d %H:%M UTC')}", f"**Model:** {self.model_used}", "", diff --git a/src/codespy/agents/reviewer/modules/__init__.py b/src/codespy/agents/reviewer/modules/__init__.py index 6c98666..b0ebcaa 100644 --- a/src/codespy/agents/reviewer/modules/__init__.py +++ b/src/codespy/agents/reviewer/modules/__init__.py @@ -1,13 +1,17 @@ """DSPy modules for code review.""" +from codespy.agents.reviewer.modules.auditor import Auditor from codespy.agents.reviewer.modules.code_reviewer import CodeReviewer from codespy.agents.reviewer.modules.doc_reviewer import DocReviewer -from codespy.agents.reviewer.modules.scope_identifier import ScopeIdentifier +from codespy.agents.reviewer.modules.scope_resolver import ScopeResolver +from codespy.agents.reviewer.modules.summarizer import Summarizer from codespy.agents.reviewer.modules.supply_chain_auditor import SupplyChainAuditor __all__ = [ + "Auditor", "CodeReviewer", "DocReviewer", - "ScopeIdentifier", + "ScopeResolver", + "Summarizer", "SupplyChainAuditor", ] diff --git a/src/codespy/agents/reviewer/modules/auditor.py b/src/codespy/agents/reviewer/modules/auditor.py new file mode 100644 index 0000000..a0664b3 --- /dev/null +++ b/src/codespy/agents/reviewer/modules/auditor.py @@ -0,0 +1,163 @@ +"""Auditor module — assesses code quality and provides recommendation after reviews.""" + +import logging +from collections.abc import Sequence +from typing import TYPE_CHECKING + +import dspy + +from codespy.agents import SignatureContext, get_cost_tracker +from codespy.agents.context_safe import ContextSafe +from codespy.agents.memory.hippocampus import Hippocampus +from codespy.agents.reviewer.models import Issue, ReviewContext +from codespy.agents.reviewer.modules.scope_resolver import _deepest_common_folder +from codespy.config import get_settings +from codespy.config_memory import get_memory_store +from codespy.tools.git.models import ChangedFile + +if TYPE_CHECKING: + from codespy.agents.reviewer.models import ScopeResult + +logger = logging.getLogger(__name__) + + +class AuditSignature(dspy.Signature): + """Assess code quality and provide a recommendation for a pull request. + + You are a busy Principal Engineer. Be extremely terse. State facts only. + Based on the summary, changed files, and issues found during review, provide: + - An overall assessment of the code quality + - A recommendation (approve, request changes, or needs discussion) + + No polite filler. No conversational language. + """ + + pr_title: str = dspy.InputField(desc="Title of the pull request") + summary: str = dspy.InputField(desc="Summary of what this PR accomplishes") + changed_files: list[ChangedFile] = dspy.InputField( + desc="In-scope reviewable files with status and line counts" + ) + all_issues: list[Issue] = dspy.InputField( + desc="All issues found during review" + ) + + quality_assessment: str = dspy.OutputField( + desc="Overall assessment of code quality" + ) + recommendation: str = dspy.OutputField( + desc="One of: APPROVE, REQUEST_CHANGES, or NEEDS_DISCUSSION with brief justification" + ) + + +class Auditor(dspy.Module): + """Assesses code quality and recommends action after all reviews complete.""" + + def __init__(self) -> None: + super().__init__() + self._cost_tracker = get_cost_tracker() + self._settings = get_settings() + + def _call_auditor( + self, + auditor: dspy.ChainOfThought, + review_context: ReviewContext, + audit_files: list[ChangedFile], + all_issues: list[Issue], + run_id: str | None, + scopes: list["ScopeResult"] | None, + topic_ids: list[str] | None, + ) -> dspy.Prediction: + """Execute the auditor predictor (with or without Hippocampus memory).""" + question = ( + f"final audit of {review_context.pr_context.repo_slug}: " + f"pull request {review_context.pr_context.pr_number} " + f"{review_context.pr_context.pr_title}: {review_context.pr_context.summary}" + ) + + if self._settings.get_memory_enabled("audit"): + mem = Hippocampus( + auditor, + budget=self._settings.get_memory_budget("audit"), + max_reflects=self._settings.get_memory_max_reflects("audit"), + question=question, + task_name="audit", + run_id=run_id, + initial_memory=review_context.memory, + topic_ids=topic_ids, + ) + result = mem( + pr_title=review_context.pr_context.pr_title, + summary=review_context.pr_context.summary, + changed_files=audit_files, + all_issues=all_issues, + ) + mem.end_episode( + get_memory_store(self._settings), + _deepest_common_folder(scopes, review_context.pr_context.repo_slug) if scopes else f"/{review_context.pr_context.repo_slug}/", + artifacts={ + "audit": ( + f"## Quality Assessment\n\n{result.quality_assessment}\n\n" + f"## Recommendation\n\n{result.recommendation}\n" + ) + }, + ) + # Persist episode at each scope location if scopes are provided + if scopes: + store = get_memory_store(self._settings) + for scope in scopes: + path = mem.episode_file_path(scope.scope_path()) + mem.save_episode(store, path) + else: + result = auditor( + pr_title=review_context.pr_context.pr_title, + summary=review_context.pr_context.summary, + changed_files=audit_files, + all_issues=all_issues, + ) + + return result + + def forward( + self, + review_context: ReviewContext, + changed_files: Sequence[ChangedFile], + all_issues: Sequence[Issue], + run_id: str | None = None, + scopes: list["ScopeResult"] | None = None, + topic_ids: list[str] | None = None, + ) -> tuple[str, str]: + """Assess quality and recommend action. + + Args: + review_context: ReviewContext containing PR identity and inherited memory + changed_files: In-scope reviewable files + all_issues: All issues found during review + run_id: Pipeline run identifier + scopes: List of resolved scopes for per-scope episode persistence + topic_ids: Optional list of topic IDs for auto-tagging + + Returns: + Tuple of (quality_assessment, recommendation) + """ + if not self._settings.is_signature_enabled("audit"): + logger.debug("Skipping audit: disabled") + return ( + "Audit disabled.", + "NEEDS_DISCUSSION" if all_issues else "APPROVE", + ) + + auditor = ContextSafe(dspy.ChainOfThought(AuditSignature), AuditSignature, name="audit") + logger.info("Running audit...") + + with SignatureContext("audit", self._cost_tracker): + result = self._call_auditor( + auditor, + review_context, + list(changed_files), + list(all_issues), + run_id, + scopes, + topic_ids, + ) + + return result.quality_assessment, result.recommendation diff --git a/src/codespy/agents/reviewer/modules/code_reviewer.py b/src/codespy/agents/reviewer/modules/code_reviewer.py index 337ccce..2776cee 100644 --- a/src/codespy/agents/reviewer/modules/code_reviewer.py +++ b/src/codespy/agents/reviewer/modules/code_reviewer.py @@ -2,20 +2,25 @@ import asyncio import logging +from collections.abc import Sequence from pathlib import Path -from typing import Any, Sequence +from typing import Any import dspy # type: ignore[import-untyped] from codespy.agents import SignatureContext, get_cost_tracker -from codespy.agents.reviewer.models import Issue, IssueCategory, ScopeResult +from codespy.agents.context_safe import ContextSafe +from codespy.agents.memory.hippocampus import ContextMemory, Hippocampus +from codespy.agents.reviewer.models import Issue, IssueCategory, ReviewContext, ScopeResult from codespy.agents.reviewer.modules.helpers import ( MIN_CONFIDENCE, + issues_to_markdown, make_scope_relative, resolve_scope_root, restore_repo_paths, ) from codespy.config import get_settings +from codespy.config_memory import get_memory_store from codespy.tools.mcp_utils import cleanup_mcp_contexts, connect_mcp_server logger = logging.getLogger(__name__) @@ -141,9 +146,6 @@ class CodeReviewSignature(dspy.Signature): class CodeReviewer(dspy.Module): """Unified code reviewer — defects, security, and smells in a single pass. - Merges DefectDetector and SmellDetector into one agent to avoid redundant - README reads, tool sessions, and input token costs per scope. - MCP tools are scope-restricted: for each scope, tools are rooted at repo_path/scope.subroot so the agent cannot access files outside the scope. """ @@ -165,7 +167,7 @@ async def _create_tools( caller = "code_reviewer" tools.extend(await connect_mcp_server( - tools_dir / "filesystem" / "server.py", + tools_dir / "storage" / "filesystem" / "server.py", [scope_root_str], contexts, caller, )) tools.extend(await connect_mcp_server( @@ -179,20 +181,28 @@ async def _create_tools( return tools, contexts async def aforward( - self, scopes: Sequence[ScopeResult], repo_path: Path - ) -> list[Issue]: + self, + scopes: Sequence[ScopeResult], + review_context: ReviewContext, + ) -> tuple[list[Issue], ContextMemory | None]: """Analyze scopes for defects, security issues, and code smells. Args: scopes: List of identified scopes with their changed files - repo_path: Path to the cloned repository + review_context: ReviewContext containing PR identity, inherited memory, + and runtime pipeline metadata (repo_path, run_id, mr) Returns: - List of bug, security, and smell issues found across all scopes + Tuple of (list of issues, merged context memory or None) """ + # Local bindings from review_context metadata + repo_path = review_context.metadata.repo_path + run_id = review_context.metadata.run_id + pr = review_context.metadata.pr + if not self._settings.is_signature_enabled("code_review"): logger.debug("Skipping code_review: disabled") - return [] + return [], review_context.memory # Determine which categories are active categories: list[IssueCategory] = [] @@ -203,9 +213,10 @@ async def aforward( changed_scopes = [s for s in scopes if s.has_changes and s.changed_files] if not changed_scopes: logger.info("No scopes with changes for code review") - return [] + return [], review_context.memory all_issues: list[Issue] = [] + scope_memories: list[ContextMemory] = [] max_iters = self._settings.get_max_iters("code_review") total_files = sum(len(s.changed_files) for s in changed_scopes) @@ -218,49 +229,96 @@ async def aforward( scope_root = resolve_scope_root(repo_path, scope.subroot) tools, contexts = await self._create_tools(scope_root) try: - agent = dspy.ReAct( - signature=CodeReviewSignature, + agent = ContextSafe( + dspy.ReAct( + signature=CodeReviewSignature, + tools=tools, + max_iters=max_iters, + ), + CodeReviewSignature, tools=tools, - max_iters=max_iters, + name="code_review", ) scoped = make_scope_relative(scope) logger.info( f" Code review: scope {scope.subroot} " f"({len(scope.changed_files)} files)" ) + mem: Hippocampus | None = None async with SignatureContext("code_review", self._cost_tracker): - result = await agent.acall( - scope=scoped, - categories=categories, - ) - - issues = [ - issue for issue in (result.issues or []) - if issue.confidence >= MIN_CONFIDENCE - ] + if self._settings.get_memory_enabled("code_review"): + question = ( + f"review code change of {scope.repo}: {scope.subroot}: " + f"pull request {review_context.pr_context.pr_number} {review_context.pr_context.pr_title}: {review_context.pr_context.summary}" + ) + topic_ids = [scope.topic(pr.repo_full_name).id] if pr else [] + mem = Hippocampus( + agent, + budget=self._settings.get_memory_budget("code_review"), + max_reflects=self._settings.get_memory_max_reflects("code_review"), + question=question, + task_name="code_review", + run_id=run_id, + initial_memory=review_context.memory, + topic_ids=topic_ids, + ) + result = await mem.aforward( + scope=scoped, + categories=categories, + ) + issues = [ + issue for issue in (result.issues or []) + if issue.confidence >= MIN_CONFIDENCE + ] + await mem.aend_episode( + get_memory_store(self._settings), + scope.scope_path(), + artifacts={"review": issues_to_markdown(issues)}, + ) + # Collect scope's context memory + if mem: + scope_memories.append(mem.cmem.model_copy(deep=True)) + else: + result = await agent.acall( + scope=scoped, + categories=categories, + ) + issues = [ + issue for issue in (result.issues or []) + if issue.confidence >= MIN_CONFIDENCE + ] restore_repo_paths(issues, scope.subroot) all_issues.extend(issues) logger.debug( f" Scope {scope.subroot}: {len(issues)} code review issues" ) except Exception as e: - logger.error(f"Code review failed for scope {scope.subroot}: {e}") + logger.error(f"Code review failed for scope {scope.subroot}: {e}", exc_info=True) finally: await cleanup_mcp_contexts(contexts) logger.info(f"Code review found {len(all_issues)} issues") - return all_issues + # Merge all scope context memories into one module-level memory + merged_memory = ( + ContextMemory.merge(*scope_memories) + if scope_memories + else review_context.memory + ) + return all_issues, merged_memory def forward( - self, scopes: Sequence[ScopeResult], repo_path: Path - ) -> list[Issue]: + self, + scopes: Sequence[ScopeResult], + review_context: ReviewContext, + ) -> tuple[list[Issue], ContextMemory | None]: """Analyze scopes for code issues (sync wrapper). Args: scopes: List of identified scopes with their changed files - repo_path: Path to the cloned repository + review_context: ReviewContext containing PR identity, inherited memory, + and runtime pipeline metadata (repo_path, run_id, mr) Returns: - List of bug, security, and smell issues found across all scopes + Tuple of (list of issues, merged context memory or None) """ - return asyncio.run(self.aforward(scopes, repo_path)) + return asyncio.run(self.aforward(scopes, review_context)) diff --git a/src/codespy/agents/reviewer/modules/doc_extractor.py b/src/codespy/agents/reviewer/modules/doc_extractor.py index fd4640e..1e63ed9 100644 --- a/src/codespy/agents/reviewer/modules/doc_extractor.py +++ b/src/codespy/agents/reviewer/modules/doc_extractor.py @@ -4,8 +4,7 @@ import re from pathlib import Path -from codespy.tools.filesystem.client import FileSystem -from codespy.tools.filesystem.models import EntryType, TreeNode +from codespy.tools.storage import EntryType, FileSystem, TreeNode logger = logging.getLogger(__name__) @@ -60,6 +59,9 @@ def extract_documentation(scope_root: Path) -> str: Concatenated documentation with ``=== filename ===`` headers, or empty string if no documentation exists. """ + if not scope_root.exists(): + logger.debug("Scope root does not exist, skipping: %s", scope_root) + return "" fs = FileSystem(scope_root, create_if_missing=False) # One tree scan — depth 2 covers root files + immediate subdirs. @@ -73,8 +75,24 @@ def extract_documentation(scope_root: Path) -> str: for path in doc_paths: try: content = fs.read_file(path) - parts.append(f"=== {path} ===\n{content.content}") except Exception as e: # noqa: BLE001 + # read_file returns an error Content rather than raising for missing + # or unreadable files, but path resolution and stat() can still raise. logger.warning(f"Could not read doc file {path}: {e}") + continue + + # read_file signals failure via Content.error, leaving content empty. An + # unchecked append would emit a header with a blank body, which reads to + # the LLM as "this doc exists and is empty" rather than "not available" + # — prompting false "add missing docs" findings. + if not content.success: + logger.debug(f"Skipping unreadable doc file {path}: {content.error}") + continue + + if not content.content.strip(): + logger.debug(f"Skipping empty doc file {path}") + continue + + parts.append(f"=== {path} ===\n{content.content}") return "\n\n".join(parts) diff --git a/src/codespy/agents/reviewer/modules/doc_reviewer.py b/src/codespy/agents/reviewer/modules/doc_reviewer.py index b9530ff..36f313f 100644 --- a/src/codespy/agents/reviewer/modules/doc_reviewer.py +++ b/src/codespy/agents/reviewer/modules/doc_reviewer.py @@ -2,21 +2,25 @@ import asyncio import logging -from pathlib import Path -from typing import Sequence +from collections.abc import Sequence import dspy # type: ignore[import-untyped] from codespy.agents import SignatureContext, get_cost_tracker -from codespy.agents.reviewer.models import Issue, IssueCategory, ScopeResult +from codespy.agents.context_safe import ContextSafe +from codespy.agents.memory.hippocampus import ContextMemory, Hippocampus +from codespy.agents.reviewer.models import Issue, IssueCategory, ReviewContext, ScopeResult from codespy.agents.reviewer.modules.doc_extractor import extract_documentation from codespy.agents.reviewer.modules.helpers import ( MIN_CONFIDENCE, + build_patches, + issues_to_markdown, make_scope_relative, resolve_scope_root, restore_repo_paths, ) from codespy.config import get_settings +from codespy.config_memory import get_memory_store logger = logging.getLogger(__name__) @@ -64,6 +68,7 @@ class DocReviewSignature(dspy.Signature): OUTPUT RULES: - Set category to "documentation" + - filename: the documentation file that needs updating (use path from === path === headers) - description: ≤25 words, imperative tone ("Update X section", "Add Y to README") - Empty list if documentation is up to date. No approval text ("LGTM", "looks good") - No polite or conversational language @@ -83,6 +88,8 @@ class DocReviewSignature(dspy.Signature): issues: list[Issue] = dspy.OutputField( desc="Documentation issues. Category must be 'documentation'. " + "Each issue MUST include 'filename' set to the documentation file that needs " + "updating (from the === filename === headers in the documentation input). " "Titles <10 words. Descriptions ≤25 words, imperative. Empty list if none." ) @@ -102,33 +109,38 @@ def __init__(self) -> None: self._settings = get_settings() def _build_patches(self, scope: ScopeResult) -> str: - """Build compact patches representation.""" - parts: list[str] = [] - for f in scope.changed_files: - if f.patch: - parts.append(f"--- {f.filename} ---\n{f.patch}") - return "\n\n".join(parts) + """Build patches representation for review.""" + return build_patches(scope.changed_files) async def aforward( - self, scopes: Sequence[ScopeResult], repo_path: Path - ) -> list[Issue]: + self, + scopes: Sequence[ScopeResult], + review_context: ReviewContext, + ) -> tuple[list[Issue], ContextMemory | None]: """Analyze scopes for documentation issues. Args: scopes: List of identified scopes with their changed files - repo_path: Path to the cloned repository + review_context: ReviewContext containing PR identity, inherited memory, + and runtime pipeline metadata (repo_path, run_id, mr) Returns: - List of documentation issues found across all scopes + Tuple of (list of issues, merged context memory or None) """ + # Local bindings from review_context metadata + repo_path = review_context.metadata.repo_path + run_id = review_context.metadata.run_id + pr = review_context.metadata.pr + if not self._settings.is_signature_enabled("doc"): logger.debug("Skipping doc: disabled") - return [] + return [], review_context.memory changed_scopes = [s for s in scopes if s.has_changes and s.changed_files] if not changed_scopes: logger.info("No scopes with changes for doc review") - return [] + return [], review_context.memory all_issues: list[Issue] = [] + scope_memories: list[ContextMemory] = [] total_files = sum(len(s.changed_files) for s in changed_scopes) logger.info( f"Doc review for {len(changed_scopes)} scopes " @@ -136,12 +148,17 @@ async def aforward( ) for scope in changed_scopes: scope_root = resolve_scope_root(repo_path, scope.subroot) + if not scope_root.exists(): + logger.debug( + f" Scope directory does not exist (deleted/moved files): {scope.subroot}" + ) + continue # Step 1: Extract documentation (deterministic — no LLM) logger.info(f" Doc extraction: scope {scope.subroot}") try: documentation = extract_documentation(scope_root) except Exception as e: - logger.error(f"Doc extraction failed for scope {scope.subroot}: {e}") + logger.error(f"Doc extraction failed for scope {scope.subroot}: {e}", exc_info=True) documentation = "" if not documentation.strip(): logger.debug( @@ -155,43 +172,87 @@ async def aforward( logger.debug(f" No patches in {scope.subroot}, skipping doc review") continue try: - reviewer = dspy.ChainOfThought(DocReviewSignature) + reviewer = ContextSafe(dspy.ChainOfThought(DocReviewSignature), DocReviewSignature, name="doc") logger.info( f" Doc review: scope {scope.subroot} " f"({len(scope.changed_files)} files)" ) + mem: Hippocampus | None = None async with SignatureContext("doc", self._cost_tracker): - result = await asyncio.to_thread( - reviewer, - patches=patches, - documentation=documentation, - categories=[IssueCategory.DOCUMENTATION], - ) - issues = [ - issue for issue in (result.issues or []) - if issue.confidence >= MIN_CONFIDENCE - ] + if self._settings.get_memory_enabled("doc"): + question = ( + f"review documentation of {scope.repo}: {scope.subroot}: " + f"pull request {review_context.pr_context.pr_number} {review_context.pr_context.pr_title}: {review_context.pr_context.summary}" + ) + topic_ids = [scope.topic(pr.repo_full_name).id] if pr else [] + mem = Hippocampus( + reviewer, + budget=self._settings.get_memory_budget("doc"), + max_reflects=self._settings.get_memory_max_reflects("doc"), + question=question, + task_name="doc", + run_id=run_id, + initial_memory=review_context.memory, + topic_ids=topic_ids, + ) + result = await mem.aforward( + patches=patches, + documentation=documentation, + categories=[IssueCategory.DOCUMENTATION], + ) + issues = [ + issue for issue in (result.issues or []) + if issue.confidence >= MIN_CONFIDENCE + ] + await mem.aend_episode( + get_memory_store(self._settings), + scope.scope_path(), + artifacts={"review": issues_to_markdown(issues)}, + ) + # Collect scope's context memory + if mem: + scope_memories.append(mem.cmem.model_copy(deep=True)) + else: + result = await asyncio.to_thread( + reviewer, + patches=patches, + documentation=documentation, + categories=[IssueCategory.DOCUMENTATION], + ) + issues = [ + issue for issue in (result.issues or []) + if issue.confidence >= MIN_CONFIDENCE + ] restore_repo_paths(issues, scope.subroot) all_issues.extend(issues) logger.debug( f" Scope {scope.subroot}: {len(issues)} doc issues" ) except Exception as e: - logger.error(f"Doc review failed for scope {scope.subroot}: {e}") + logger.error(f"Doc review failed for scope {scope.subroot}: {e}", exc_info=True) logger.info(f"Doc review found {len(all_issues)} issues") - return all_issues + # Merge all scope context memories into one module-level memory + merged_memory = ( + ContextMemory.merge(*scope_memories) + if scope_memories + else review_context.memory + ) + return all_issues, merged_memory def forward( - self, scopes: Sequence[ScopeResult], repo_path: Path - ) -> list[Issue]: + self, + scopes: Sequence[ScopeResult], + review_context: ReviewContext, + ) -> tuple[list[Issue], ContextMemory | None]: """Analyze scopes for documentation issues (sync wrapper). Args: scopes: List of identified scopes with their changed files - repo_path: Path to the cloned repository + review_context: ReviewContext containing PR identity, inherited memory, + and runtime pipeline metadata (repo_path, run_id, mr) Returns: - List of documentation issues found across all scopes + Tuple of (list of issues, merged context memory or None) """ - return asyncio.run(self.aforward(scopes, repo_path)) + return asyncio.run(self.aforward(scopes, review_context)) diff --git a/src/codespy/agents/reviewer/modules/helpers.py b/src/codespy/agents/reviewer/modules/helpers.py index 5e71b3a..ade8f7c 100644 --- a/src/codespy/agents/reviewer/modules/helpers.py +++ b/src/codespy/agents/reviewer/modules/helpers.py @@ -4,11 +4,12 @@ import logging import os +from collections.abc import Sequence from pathlib import Path from typing import TYPE_CHECKING -from codespy.tools.git.models import ChangedFile from codespy.agents.reviewer.models import Issue +from codespy.tools.git.models import ChangedFile if TYPE_CHECKING: from codespy.agents.reviewer.models import ScopeResult @@ -58,10 +59,10 @@ def is_markdown_file(filename: str) -> bool: def get_language(file: ChangedFile) -> str: """Get the programming language for a file based on extension. - + Args: file: The changed file - + Returns: Language name or "Unknown" """ @@ -118,7 +119,8 @@ def make_scope_relative(scope: ScopeResult) -> ScopeResult: New ScopeResult with scope-relative file paths in changed_files. The subroot is set to "." since paths are now relative to it. """ - from codespy.agents.reviewer.models import PackageManifest, ScopeResult as SR + from codespy.agents.reviewer.models import PackageManifest + from codespy.agents.reviewer.models import ScopeResult as SR if scope.subroot == ".": return scope # Already at repo root, no transformation needed @@ -151,15 +153,16 @@ def make_scope_relative(scope: ScopeResult) -> ScopeResult: dependencies_changed=scope.package_manifest.dependencies_changed, ) return SR( + repo=scope.repo, subroot=".", scope_type=scope.scope_type, has_changes=scope.has_changes, is_dependency=scope.is_dependency, - confidence=scope.confidence, language=scope.language, package_manifest=manifest, changed_files=relative_files, reason=scope.reason, + skills=scope.skills, ) @@ -179,3 +182,62 @@ def restore_repo_paths(issues: list[Issue], subroot: str) -> None: for issue in issues: if issue.filename and not issue.filename.startswith(prefix): issue.filename = prefix + issue.filename + + +def build_patches(files: Sequence[ChangedFile]) -> str: + """Build a patches string from a sequence of changed files. + + Each patch is prefixed with its filename in the format: + --- {filename} --- + {patch content} + + Args: + files: Sequence of ChangedFile objects + + Returns: + Concatenated patches string, or empty string if no patches + """ + parts: list[str] = [] + for f in files: + if f.patch: + parts.append(f"--- {f.filename} ---\n{f.patch}") + return "\n\n".join(parts) + + +def issues_to_markdown(issues: list[Issue]) -> str: + """Format a list of issues as a compact Markdown report. + + Intended as an ``Episode`` artifact (see ``Hippocampus.aend_episode``) + so a scope's episode carries a human-readable snapshot of what the + module found for that call, alongside the consolidated context memory. + + Args: + issues: Issues found for a given scope/call. + + Returns: + Markdown text. If ``issues`` is empty, a short "no issues" note. + """ + if not issues: + return "No issues found." + + lines = [f"## Issues ({len(issues)})", ""] + for issue in issues: + lines.extend([ + f"### {issue.title}", + "", + f"**Location:** `{issue.location}`", + f"**Category:** {issue.category.value}", + f"**Severity:** {issue.severity.value}", + "", + issue.description, + "", + ]) + if issue.suggestion: + lines.extend(["**Suggestion:**", issue.suggestion, ""]) + if issue.cwe_id: + lines.append(f"**Reference:** {issue.cwe_id}") + lines.append("") + lines.append("---") + lines.append("") + return "\n".join(lines) + diff --git a/src/codespy/agents/reviewer/modules/manifest_parser.py b/src/codespy/agents/reviewer/modules/manifest_parser.py new file mode 100644 index 0000000..921c912 --- /dev/null +++ b/src/codespy/agents/reviewer/modules/manifest_parser.py @@ -0,0 +1,880 @@ +"""Package manifest parser for extracting package names from various manifest files.""" + +from __future__ import annotations + +import configparser +import json +import re +from pathlib import Path +from xml.etree import ElementTree as ET + +# Mapping of package manager to ecosystem name +PACKAGE_MANAGER_TO_ECOSYSTEM: dict[str, str] = { + "npm": "npm", + "go": "Go", + "pip": "PyPI", + "cargo": "crates.io", + "maven": "Maven", + "gradle": "Maven", + "sbt": "Maven", + "composer": "Packagist", + "bundler": "RubyGems", + "dotnet": "NuGet", + "swift": "SwiftURL", + "pub": "Pub", + "mix": "Hex", + "helm": "Helm", + "clojure": "Clojure", + "leiningen": "Clojure", + "stack": "Hackage", + "cabal": "Hackage", + "dune": "opam", + "zig": "Zig", + "cpan": "CPAN", + "r": "CRAN", +} + +# Git hosts for repo inference +_GIT_HOSTS = ("github.com/", "gitlab.com/", "bitbucket.org/") + + +def _infer_repo_from_url(url: str) -> str | None: + """Extract owner/repo from git URL (e.g., https://github.com/owner/repo.git).""" + for host in _GIT_HOSTS: + for scheme in (f"https://{host}", f"http://{host}", f"git@{host.rstrip('/')}:"): + if url.startswith(scheme): + path = url[len(scheme):].rstrip("/").removesuffix(".git") + parts = path.split("/") + if len(parts) >= 2: + return f"{parts[0]}/{parts[1]}" + return None + + +def _infer_repo_from_name(name: str) -> str | None: + """Extract owner/repo from a name with git host prefix (e.g., Go module).""" + for host in _GIT_HOSTS: + if name.startswith(host): + parts = name[len(host):].split("/") + if len(parts) >= 2: + return f"{parts[0]}/{parts[1]}" + return None + + +def _infer_repo_from_path(path: str) -> str | None: + """Extract owner/repo from a file path containing a git host (vendored deps).""" + for host in _GIT_HOSTS: + idx = path.find(host) + if idx >= 0: + remainder = path[idx + len(host):] + parts = remainder.split("/") + if len(parts) >= 2: + return f"{parts[0]}/{parts[1]}" + return None + + +def infer_repo_from_name(name: str) -> str | None: + """Public wrapper for _infer_repo_from_name.""" + return _infer_repo_from_name(name) + + +def extract_dependencies(manifest_path: str, repo_path: Path) -> tuple[list[str], dict[str, str]]: + """Extract production dependency names and inferred source repos from manifest. + + Args: + manifest_path: Relative path to manifest file from repo root + repo_path: Path to the repository root + + Returns: + Tuple of: + - dependency_names: list of all production dep names + - dependency_repos: dict mapping dep name -> owner/repo for identifiable deps + """ + full_path = repo_path / manifest_path + if not full_path.exists(): + return [], {} + + filename = Path(manifest_path).name + + try: + if filename == "package.json": + return _extract_deps_from_package_json(full_path) + elif filename == "go.mod": + return _extract_deps_from_go_mod(full_path) + elif filename == "pyproject.toml": + return _extract_deps_from_pyproject_toml(full_path) + elif filename == "Cargo.toml": + return _extract_deps_from_cargo_toml(full_path) + elif filename == "pom.xml": + return _extract_deps_from_pom_xml(full_path) + elif filename == "composer.json": + return _extract_deps_from_composer_json(full_path) + elif filename == "pubspec.yaml": + return _extract_deps_from_pubspec_yaml(full_path) + elif filename == "Gemfile": + return _extract_deps_from_gemfile(full_path) + elif filename == "mix.exs": + return _extract_deps_from_mix_exs(full_path) + elif filename.endswith(".csproj"): + return _extract_deps_from_csproj(full_path) + elif filename == "Package.swift": + return _extract_deps_from_swift_package(full_path) + elif filename in ("build.gradle", "build.gradle.kts"): + return _extract_deps_from_gradle(full_path) + elif filename == "setup.cfg": + return _extract_deps_from_setup_cfg(full_path) + except Exception: + return [], {} + + return [], {} + + +def _extract_deps_from_package_json(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from package.json (production only, skip dev/peer/optional).""" + try: + with open(path, encoding="utf-8") as f: + data = json.load(f) + + deps = data.get("dependencies", {}) + if not isinstance(deps, dict): + return [], {} + + names = list(deps.keys()) + repos: dict[str, str] = {} + + for name, spec in deps.items(): + if isinstance(spec, str): + # Parse git URLs: "github:owner/repo" or "git+https://..." + if spec.startswith("github:"): + repo_path = spec[7:].split("#")[0] # Remove any #ref + if "/" in repo_path: + repos[name] = repo_path + elif spec.startswith("git+"): + inferred = _infer_repo_from_url(spec[4:]) + if inferred: + repos[name] = inferred + + return names, repos + except (json.JSONDecodeError, UnicodeDecodeError, OSError): + return [], {} + + +def _extract_deps_from_go_mod(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from go.mod (filter // indirect lines).""" + try: + with open(path, encoding="utf-8") as f: + content = f.read() + + names: list[str] = [] + repos: dict[str, str] = {} + + # Parse require block + in_require = False + for line in content.split("\n"): + line = line.strip() + if line.startswith("require ("): + in_require = True + continue + if in_require and line == ")": + in_require = False + continue + if not in_require and line.startswith("require "): + # Single-line require + parts = line[8:].strip().split() + if parts: + line = parts[0] + else: + continue + + if in_require or (line and not line.startswith("require ")): + # Skip indirect deps + if "// indirect" in line: + continue + # Extract module path + parts = line.split() + if parts: + module_path = parts[0] + names.append(module_path) + # Go modules always have host in path + inferred = _infer_repo_from_name(module_path) + if inferred: + repos[module_path] = inferred + + return names, repos + except (UnicodeDecodeError, OSError): + return [], {} + + +def _strip_pep508_extras(name: str) -> str: + """Strip extras and version specifiers from PEP 508 dependency name.""" + # Handle name[extra] -> name + if "[" in name: + name = name.split("[")[0] + # Handle version specifiers (>=, ==, ~=, etc.) + for op in (">=", "<=", ">", "<", "==", "!=", "~=", "==="): + if op in name: + name = name.split(op)[0].strip() + return name.strip() + + +def _extract_deps_from_pyproject_toml(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from pyproject.toml (PEP 508 or Poetry).""" + try: + import tomllib + + with open(path, "rb") as f: + data = tomllib.load(f) + + names: list[str] = [] + repos: dict[str, str] = {} + + # Try [project.dependencies] (PEP 508) + project = data.get("project") + if isinstance(project, dict): + deps = project.get("dependencies", []) + if isinstance(deps, list): + for dep in deps: + if isinstance(dep, str): + name = _strip_pep508_extras(dep) + if name and name != "python": + names.append(name) + + # Try [tool.poetry.dependencies] + tool = data.get("tool") + if isinstance(tool, dict): + poetry = tool.get("poetry") + if isinstance(poetry, dict): + poetry_deps = poetry.get("dependencies", {}) + if isinstance(poetry_deps, dict): + for name, spec in poetry_deps.items(): + if name == "python": + continue + names.append(name) + # Check for git or path source + if isinstance(spec, dict): + git_url = spec.get("git") + if git_url and isinstance(git_url, str): + inferred = _infer_repo_from_url(git_url) + if inferred: + repos[name] = inferred + path_val = spec.get("path") + if path_val and isinstance(path_val, str): + inferred = _infer_repo_from_path(path_val) + if inferred: + repos[name] = inferred + + return names, repos + except Exception: + return [], {} + + +def _extract_deps_from_cargo_toml(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from Cargo.toml (skip dev-dependencies, build-dependencies).""" + try: + import tomllib + + with open(path, "rb") as f: + data = tomllib.load(f) + + names: list[str] = [] + repos: dict[str, str] = {} + + deps = data.get("dependencies", {}) + if isinstance(deps, dict): + for name, spec in deps.items(): + names.append(name) + if isinstance(spec, dict): + git_url = spec.get("git") + if git_url and isinstance(git_url, str): + inferred = _infer_repo_from_url(git_url) + if inferred: + repos[name] = inferred + + return names, repos + except Exception: + return [], {} + + +def _extract_deps_from_pom_xml(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from pom.xml (skip test scope).""" + try: + tree = ET.parse(path) + root = tree.getroot() + + ns = {"m": "http://maven.apache.org/POM/4.0.0"} + + names: list[str] = [] + + deps = root.find("m:dependencies", ns) + if deps is None: + deps = root.find("dependencies") + + if deps is not None: + for dep in deps.findall("m:dependency", ns) if deps else []: + scope = dep.find("m:scope", ns) + if scope is not None and scope.text == "test": + continue + group = dep.find("m:groupId", ns) + artifact = dep.find("m:artifactId", ns) + if group is not None and artifact is not None: + names.append(f"{group.text}:{artifact.text}") + + return names, {} + except ET.ParseError: + return [], {} + + +def _extract_deps_from_composer_json(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from composer.json (exclude php, ext-*).""" + try: + with open(path, encoding="utf-8") as f: + data = json.load(f) + + require = data.get("require", {}) + if not isinstance(require, dict): + return [], {} + + names = [name for name in require + if not name.startswith("php") and not name.startswith("ext-")] + + return names, {} + except (json.JSONDecodeError, UnicodeDecodeError, OSError): + return [], {} + + +def _extract_deps_from_pubspec_yaml(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from pubspec.yaml (exclude flutter packages).""" + try: + import yaml + + with open(path, encoding="utf-8") as f: + data = yaml.safe_load(f) + + deps = data.get("dependencies", {}) + if not isinstance(deps, dict): + return [], {} + + excluded = {"flutter", "flutter_test", "flutter_localizations"} + names: list[str] = [] + repos: dict[str, str] = {} + + for name, spec in deps.items(): + if name in excluded: + continue + names.append(name) + if isinstance(spec, dict): + git_url = spec.get("git") + if isinstance(git_url, str): + inferred = _infer_repo_from_url(git_url) + if inferred: + repos[name] = inferred + elif isinstance(git_url, dict): + url = git_url.get("url") + if url and isinstance(url, str): + inferred = _infer_repo_from_url(url) + if inferred: + repos[name] = inferred + + return names, repos + except Exception: + return [], {} + + +def _extract_deps_from_gemfile(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from Gemfile (skip dev/test groups).""" + try: + with open(path, encoding="utf-8") as f: + content = f.read() + + names: list[str] = [] + repos: dict[str, str] = {} + + # Track if we're in a dev/test group + in_dev_group = False + group_depth = 0 + + for line in content.split("\n"): + line_stripped = line.strip() + + # Track group blocks + if line_stripped.startswith("group "): + if ":development" in line_stripped or ":test" in line_stripped: + in_dev_group = True + group_depth += 1 + continue + + if line_stripped == "end" and group_depth > 0: + group_depth -= 1 + if group_depth == 0: + in_dev_group = False + continue + + if in_dev_group: + continue + + # Parse gem lines + match = re.match(r"gem\s+['\"]([^'\"]+)['\"]", line_stripped) + if match: + name = match.group(1) + names.append(name) + + # Check for git or github option + git_match = re.search(r"git:\s*['\"]([^'\"]+)['\"]", line) + if git_match: + inferred = _infer_repo_from_url(git_match.group(1)) + if inferred: + repos[name] = inferred + + github_match = re.search(r"github:\s*['\"]([^'\"]+)['\"]", line) + if github_match: + gh_path = github_match.group(1) + repos[name] = gh_path if "/" in gh_path else f"{gh_path}/{gh_path}" + + return names, repos + except (UnicodeDecodeError, OSError): + return [], {} + + +def _extract_deps_from_mix_exs(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from mix.exs (skip dev/test only).""" + try: + with open(path, encoding="utf-8") as f: + content = f.read() + + names: list[str] = [] + repos: dict[str, str] = {} + + # Find deps function + deps_match = re.search(r"defp?\s+deps\s*do\s*\[(.*?)\]\s*end", content, re.DOTALL) + if not deps_match: + return [], {} + + deps_block = deps_match.group(1) + + # Parse each dep tuple + for dep_match in re.finditer(r"\{([^}]+)\}", deps_block): + dep_str = dep_match.group(1) + # Skip if only: :dev or only: :test + if "only: :dev" in dep_str or "only: :test" in dep_str: + continue + + # Extract name (first atom or string) + name_match = re.match(r":([a-z_][a-zA-Z0-9_]*)|\"([^\"]+)\"", dep_str.strip()) + if name_match: + name = name_match.group(1) or name_match.group(2) + if name: + names.append(name) + + # Check for github option + gh_match = re.search(r"github:\s*\"([^\"]+)\"", dep_str) + if gh_match: + gh_path = gh_match.group(1) + repos[name] = gh_path if "/" in gh_path else f"{gh_path}/{gh_path}" + + return names, repos + except (UnicodeDecodeError, OSError): + return [], {} + + +def _extract_deps_from_csproj(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from .csproj (skip PrivateAssets=All).""" + try: + tree = ET.parse(path) + root = tree.getroot() + + ns = {"p": "http://schemas.microsoft.com/developer/msbuild/2003"} + + names: list[str] = [] + + for ref in root.findall(".//p:PackageReference", ns): + private = ref.get("PrivateAssets") + if private == "All": + continue + include = ref.get("Include") + if include: + names.append(include) + + # Also try without namespace + if not names: + for ref in root.findall(".//PackageReference"): + private = ref.get("PrivateAssets") + if private == "All": + continue + include = ref.get("Include") + if include: + names.append(include) + + return names, {} + except ET.ParseError: + return [], {} + + +def _extract_deps_from_swift_package(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from Package.swift.""" + try: + with open(path, encoding="utf-8") as f: + content = f.read() + + names: list[str] = [] + repos: dict[str, str] = {} + + # Match .package(url: "...", ...) + for match in re.finditer(r'\.package\s*\([^)]*url:\s*["\']([^"\']+)["\']', content): + url = match.group(1) + inferred = _infer_repo_from_url(url) + if inferred: + # Use repo name as dep name for Swift + dep_name = inferred.split("/")[-1] + names.append(dep_name) + repos[dep_name] = inferred + else: + # Extract name from URL + parts = url.rstrip("/").split("/") + if parts: + name = parts[-1].removesuffix(".git") + names.append(name) + + return names, repos + except (UnicodeDecodeError, OSError): + return [], {} + + +def _extract_deps_from_gradle(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from build.gradle/build.gradle.kts (skip test/debug).""" + try: + with open(path, encoding="utf-8") as f: + content = f.read() + + names: list[str] = [] + + # Match implementation, api, compile dependencies + for match in re.finditer(r"(implementation|api|compile)\s*['\"]([^'\"]+)['\"]", content): + coord = match.group(2) + # Skip test/debug variants + if not coord.startswith("test") and not coord.startswith("debug"): + names.append(coord) + + # Match Kotlin DSL: implementation("...") + for match in re.finditer(r"(implementation|api)\s*\(\s*['\"]([^'\"]+)['\"]\s*\)", content): + coord = match.group(2) + if not coord.startswith("test") and not coord.startswith("debug"): + names.append(coord) + + return names, {} + except (UnicodeDecodeError, OSError): + return [], {} + + +def _extract_deps_from_setup_cfg(path: Path) -> tuple[list[str], dict[str, str]]: + """Extract deps from setup.cfg [options] install_requires.""" + try: + config = configparser.ConfigParser() + config.read(path, encoding="utf-8") + + names: list[str] = [] + + if config.has_option("options", "install_requires"): + deps_str = config.get("options", "install_requires") + for line in deps_str.strip().split("\n"): + line = line.strip() + if line and not line.startswith("#"): + name = _strip_pep508_extras(line) + if name: + names.append(name) + + return names, {} + except (configparser.Error, UnicodeDecodeError, OSError): + return [], {} + + +def extract_package_name(manifest_path: str, repo_path: Path) -> str | None: + """Extract package name from manifest file. Returns None on failure. + + Args: + manifest_path: Relative path to manifest file from repo root + repo_path: Path to the repository root + + Returns: + Package name string or None if extraction fails + """ + full_path = repo_path / manifest_path + if not full_path.exists(): + return None + + filename = Path(manifest_path).name + + try: + if filename == "package.json" or filename == "composer.json": + return _extract_from_json(full_path, ["name"]) + elif filename == "go.mod": + return _extract_from_go_mod(full_path) + elif filename == "pyproject.toml": + return _extract_from_pyproject_toml(full_path) + elif filename == "Cargo.toml": + return _extract_from_toml(full_path, ["package", "name"]) + elif filename == "pubspec.yaml" or filename == "Chart.yaml": + return _extract_from_yaml(full_path, ["name"]) + elif filename == "pom.xml": + return _extract_from_pom_xml(full_path) + elif filename == "setup.cfg": + return _extract_from_setup_cfg(full_path) + elif filename.endswith(".csproj") or filename.endswith(".fsproj") or filename.endswith(".vbproj"): + return _extract_from_dotnet_proj(full_path) + elif filename in ("build.gradle", "build.gradle.kts"): + return _extract_from_gradle(repo_path, manifest_path) + elif filename == "Gemfile": + return _extract_from_gemfile(full_path) + elif filename == "Package.swift": + return _extract_from_swift_package(full_path) + elif filename == "mix.exs": + return _extract_from_mix_exs(full_path) + except Exception: + return None + + return None + + +def _extract_from_json(path: Path, keys: list[str]) -> str | None: + """Extract value from JSON file following key path.""" + try: + with open(path, encoding="utf-8") as f: + data = json.load(f) + value = data + for key in keys: + if not isinstance(value, dict): + return None + value = value.get(key) + if value is None: + return None + return value if isinstance(value, str) else None + except (json.JSONDecodeError, UnicodeDecodeError, OSError): + return None + + +def _extract_from_go_mod(path: Path) -> str | None: + """Extract module name from go.mod file.""" + try: + with open(path, encoding="utf-8") as f: + first_line = f.readline().strip() + match = re.match(r"^module\s+(\S+)", first_line) + return match.group(1) if match else None + except (UnicodeDecodeError, OSError): + return None + + +def _extract_from_toml(path: Path, keys: list[str]) -> str | None: + """Extract value from TOML file following key path.""" + try: + import tomllib + + with open(path, "rb") as f: + data = tomllib.load(f) + value = data + for key in keys: + if not isinstance(value, dict): + return None + value = value.get(key) + if value is None: + return None + return value if isinstance(value, str) else None + except Exception: + return None + + +def _extract_from_pyproject_toml(path: Path) -> str | None: + """Extract package name from pyproject.toml. + + Tries [project][name] first, then [tool][poetry][name]. + """ + try: + import tomllib + + with open(path, "rb") as f: + data = tomllib.load(f) + + # Try [project][name] first + project = data.get("project") + if isinstance(project, dict): + name = project.get("name") + if isinstance(name, str): + return name + + # Fall back to [tool][poetry][name] + tool = data.get("tool") + if isinstance(tool, dict): + poetry = tool.get("poetry") + if isinstance(poetry, dict): + name = poetry.get("name") + if isinstance(name, str): + return name + + return None + except Exception: + return None + + +def _extract_from_yaml(path: Path, keys: list[str]) -> str | None: + """Extract value from YAML file following key path.""" + try: + import yaml + + with open(path, encoding="utf-8") as f: + data = yaml.safe_load(f) + value = data + for key in keys: + if not isinstance(value, dict): + return None + value = value.get(key) + if value is None: + return None + return value if isinstance(value, str) else None + except Exception: + return None + + +def _extract_from_pom_xml(path: Path) -> str | None: + """Extract package name from Maven pom.xml as 'groupId:artifactId'.""" + try: + tree = ET.parse(path) + root = tree.getroot() + + # Handle namespaced XML + ns = {"m": "http://maven.apache.org/POM/4.0.0"} + + group_id = root.find("m:groupId", ns) + if group_id is None: + group_id = root.find("groupId") + + artifact_id = root.find("m:artifactId", ns) + if artifact_id is None: + artifact_id = root.find("artifactId") + + if group_id is not None and artifact_id is not None: + return f"{group_id.text}:{artifact_id.text}" + elif artifact_id is not None: + return artifact_id.text + return None + except ET.ParseError: + return None + + +def _extract_from_setup_cfg(path: Path) -> str | None: + """Extract package name from setup.cfg [metadata] section.""" + try: + config = configparser.ConfigParser() + config.read(path, encoding="utf-8") + if config.has_option("metadata", "name"): + return config.get("metadata", "name") + return None + except (configparser.Error, UnicodeDecodeError, OSError): + return None + + +def _extract_from_dotnet_proj(path: Path) -> str | None: + """Extract package name from .NET project file. + + Tries PackageId first, then RootNamespace, then filename stem. + """ + try: + tree = ET.parse(path) + root = tree.getroot() + + # Handle namespaced XML + ns = {"p": "http://schemas.microsoft.com/developer/msbuild/2003"} + + # Try PackageId + package_id = root.find(".//p:PackageId", ns) + if package_id is None: + package_id = root.find(".//PackageId") + if package_id is not None and package_id.text: + return package_id.text + + # Try RootNamespace + root_ns = root.find(".//p:RootNamespace", ns) + if root_ns is None: + root_ns = root.find(".//RootNamespace") + if root_ns is not None and root_ns.text: + return root_ns.text + + # Fall back to filename stem + return path.stem + except ET.ParseError: + return path.stem + + +def _extract_from_gradle(repo_path: Path, manifest_path: str) -> str | None: + """Extract project name from Gradle settings.gradle or settings.gradle.kts. + + Gradle projects don't store the name in build.gradle - it's in settings.gradle. + This is a best-effort extraction. + """ + manifest_dir = Path(manifest_path).parent + + # Look for settings.gradle or settings.gradle.kts + settings_files = ["settings.gradle", "settings.gradle.kts"] + + for settings_file in settings_files: + settings_path = repo_path / manifest_dir / settings_file + if settings_path.exists(): + try: + with open(settings_path, encoding="utf-8") as f: + content = f.read() + # Look for rootProject.name = 'name' or rootProject.name = "name" + match = re.search( + r"rootProject\.name\s*=\s*['\"]([^'\"]+)['\"]", + content, + ) + if match: + return match.group(1) + except (UnicodeDecodeError, OSError): + continue + + return None + + +def _extract_from_gemfile(path: Path) -> str | None: + """Extract gem name from Gemfile. + + Looks for 'source' line to extract the gem name, but this is often + not present. Returns None as Gemfiles don't reliably contain a package name. + """ + # Gemfiles don't typically contain a reliable package name + # They reference gems to install, not the current package name + return None + + +def _extract_from_swift_package(path: Path) -> str | None: + """Extract package name from Package.swift. + + Looks for 'name: "..."' in the Package initialization. + """ + try: + with open(path, encoding="utf-8") as f: + content = f.read() + # Look for Package(name: "...") + match = re.search( + r"Package\s*\([^)]*name:\s*['\"]([^'\"]+)['\"]", + content, + re.DOTALL, + ) + if match: + return match.group(1) + return None + except (UnicodeDecodeError, OSError): + return None + + +def _extract_from_mix_exs(path: Path) -> str | None: + """Extract project name from mix.exs. + + Looks for 'def project do' and extracts the 'app:' value. + """ + try: + with open(path, encoding="utf-8") as f: + content = f.read() + # Look for app: :name or app: "name" + match = re.search(r"app:\s*[:\"]([^\"\s,)]+)[\"\s,)]", content) + if match: + return match.group(1) + return None + except (UnicodeDecodeError, OSError): + return None diff --git a/src/codespy/agents/reviewer/modules/scope_identifier.py b/src/codespy/agents/reviewer/modules/scope_identifier.py deleted file mode 100644 index 860fbd2..0000000 --- a/src/codespy/agents/reviewer/modules/scope_identifier.py +++ /dev/null @@ -1,326 +0,0 @@ -"""Scope identifier module for detecting code scopes in repositories.""" - -import asyncio -import logging -from pathlib import Path -from typing import Any - -import dspy # type: ignore[import-untyped] -from pydantic import BaseModel, Field - -from codespy.agents import SignatureContext, get_cost_tracker -from codespy.agents.reviewer.models import PackageManifest, ScopeResult, ScopeType -from codespy.config import get_settings -from codespy.tools.git.models import ChangedFile, MergeRequest, should_review_file -from codespy.tools.mcp_utils import cleanup_mcp_contexts, connect_mcp_server - -logger = logging.getLogger(__name__) - - -class ScopeAssignment(BaseModel): - """LLM-friendly scope assignment with string file paths. - - This intermediate model is used for LLM output since the LLM can only - produce string file paths, not full ChangedFile objects with patches/content. - It gets converted to ScopeResult with proper ChangedFile objects after LLM call. - """ - - subroot: str = Field(description="Path relative to repo root (e.g., packages/auth)") - scope_type: ScopeType = Field(description="Type of scope (library, service, etc.)") - has_changes: bool = Field( - default=False, description="Whether this scope has changed files from PR" - ) - is_dependency: bool = Field( - default=False, description="Whether this scope depends on a changed scope" - ) - confidence: float = Field( - default=0.8, ge=0.0, le=1.0, description="Confidence score for scope identification" - ) - language: str | None = Field(default=None, description="Primary language detected") - package_manifest: PackageManifest | None = Field( - default=None, description="Package manifest info if present" - ) - changed_files: list[str] = Field( - default_factory=list, description="Changed file paths belonging to this scope" - ) - reason: str = Field(description="Explanation for why this scope was identified") - - -class ScopeIdentifierSignature(dspy.Signature): - """Identify code scopes in a repository for a merge request. - - You have tools to clone the repository, explore its filesystem, and analyze code. - Your goal is to identify logical code boundaries (scopes) and assign each changed file to exactly one scope. - - STEP 1 - ANALYZE CHANGED FILE PATHS (before cloning): - The changed file paths are your MOST IMPORTANT signal for scope detection. - 1. Extract common directory prefixes from changed files to find candidate scopes - 2. Look for scope indicator patterns at ANY DEPTH in the path: - - svc/, services/, microservices/ → likely a service scope - - libs/, packages/, shared/, common/, core/ → likely a library scope - - apps/, web/, frontend/, mobile/ → likely an application scope - - scripts/, bin/, tools/, hack/, ci/, .github/, .gitlab/ → likely a script scope - 3. EXAMPLES of nested scope detection from file paths: - - Files: mono/svc/my-service-v1/internal/handler.go, mono/svc/my-service-v1/cmd/main.go - → Candidate scope: mono/svc/my-service-v1 (the "svc/" pattern indicates service) - - Files: platform/packages/auth/src/index.ts, platform/packages/auth/package.json - → Candidate scope: platform/packages/auth (the "packages/" pattern indicates library) - - Files: company/backend/services/user-api/main.go - → Candidate scope: company/backend/services/user-api - 4. Group files by their longest common directory prefix that contains a scope indicator - - STEP 2 - ACCESS THE REPOSITORY: - - If is_local is True: - The repository is ALREADY available at target_repo_path. Do NOT clone. - Skip directly to STEP 3 and use filesystem tools to explore the repo. - - If is_local is False: - Clone using clone_repository tool: - 1. Use the repo_owner, repo_name, and head_sha provided in the inputs - 2. Clone to the target_repo_path provided - 3. Derive sparse_paths from candidate scopes identified in STEP 1: - - Include each candidate scope directory - - Example: ["mono/svc/my-service-v1/", "libs/common/"] - 4. Use depth=1 for fastest clone (single commit) - - STEP 3 - VERIFY SCOPES WITH PACKAGE MANIFESTS: - For each candidate scope from STEP 1: - 1. Check if a package manifest exists at that path (go.mod, package.json, pyproject.toml, Cargo.toml, etc.) - 2. If found → CONFIRM that directory as the scope root - 3. If NOT found → Walk UP parent directories until you find a package manifest - - Example: If candidate is mono/svc/my-service-v1/internal, check: - * mono/svc/my-service-v1/internal/go.mod (not found) - * mono/svc/my-service-v1/go.mod (FOUND → this is the scope) - 4. The directory containing the package manifest is the authoritative scope root - - SCOPE TYPE CLASSIFICATION: - These patterns can appear at ANY NESTING DEPTH - not just at the repository root! - - library: Shared code that others import - * Patterns at any depth: */libs/*, */packages/*, */shared/*, */common/*, */core/* - * Multiple consumers importing from this scope - * Generic/reusable code patterns - - service: Isolated microservice with APIs - * Patterns at any depth: */services/*, */microservices/*, */svc/* - * Own package manifest, often with server/API code - * HTTP handlers, gRPC definitions, message consumers - - application: Standalone app or frontend - * Patterns at any depth: */apps/*, */web/*, */frontend/*, */mobile/* - * Entry points (main.go, index.ts, App.tsx) - * UI components, routing, state management - - script: Build/deployment scripts, tooling - * Patterns at any depth: */scripts/*, */bin/*, */tools/*, */hack/*, */ci/*, */.github/* - * Shell scripts, Makefiles, Dockerfiles - * CI/CD configuration, deployment scripts - - MONO-REPO PATTERNS (can be nested!): - - Check root AND nested package.json for workspaces configuration - - Look for lerna.json, pnpm-workspace.yaml, turbo.json at various depths - - Scope indicator directories (packages/, services/, apps/, svc/) can appear at any level: - * repo/services/api/ ← traditional mono-repo - * repo/mono/svc/user-api/ ← nested mono-repo - * repo/platform/backend/services/api/ ← deeply nested - - PACKAGE MANIFESTS TO DETECT: - - package.json (npm) with lock files: package-lock.json, yarn.lock, pnpm-lock.yaml - - pyproject.toml (pip) with lock files: poetry.lock, uv.lock - - go.mod (go) with lock file: go.sum - - Cargo.toml (cargo) with lock file: Cargo.lock - - pom.xml (maven), build.gradle (gradle), composer.json (composer), Gemfile (bundler) - - CRITICAL RULES: - 1. EVERY changed file must be assigned to exactly ONE scope - 2. Don't create overlapping scopes (parent contains child) - 3. ALWAYS prefer the most specific scope - the deepest directory with a package manifest - - If files are in mono/svc/my-service-v1/, use that as scope, NOT "." or "mono/" - 4. Use "." as scope ONLY when: - - Files are truly at the repo root with no nested project structure - - No package manifest exists at any deeper level - - Changed files span multiple unrelated directories with no common scope indicator - 5. Use tools to verify package manifest existence - don't guess - 6. Trust the file paths - if they contain svc/, services/, packages/ etc., that's a strong scope signal - - OUTPUT EFFICIENCY: Group files by common directory prefix in reasoning. Do not reason about each file path individually. - Keep each reasoning step to 1-2 sentences. - """ - - changed_files: list[str] = dspy.InputField( - desc="List of changed file paths from the MR. Use these to derive sparse_paths for efficient cloning." - ) - repo_owner: str = dspy.InputField(desc="Repository owner/namespace (e.g., 'facebook' or 'group/subgroup'). Used for cloning (remote only).") - repo_name: str = dspy.InputField(desc="Repository name (e.g., 'react'). Used for cloning (remote only).") - head_sha: str = dspy.InputField(desc="Git commit SHA to checkout. Used for cloning (remote only).") - target_repo_path: str = dspy.InputField( - desc="Absolute path to the repository. For remote reviews, clone here. For local reviews, the repo is already here." - ) - mr_title: str = dspy.InputField(desc="MR title for additional context") - mr_description: str = dspy.InputField(desc="MR description for additional context") - is_local: bool = dspy.InputField( - desc="Whether the repository is already available locally at target_repo_path. If True, skip cloning and go directly to filesystem exploration." - ) - - scopes: list[ScopeAssignment] = dspy.OutputField( - desc="Identified scopes. Every changed file must appear in exactly one scope. Use concise reasons (<2 sentences)." - ) - - -class ScopeIdentifier(dspy.Module): - """Agentic scope identifier using ReAct pattern with MCP tools. - - This module uses an LLM agent to explore the repository structure - and identify logical code scopes for focused code review. - """ - - def __init__(self) -> None: - """Initialize the scope identifier.""" - super().__init__() - self._cost_tracker = get_cost_tracker() - self._settings = get_settings() - - async def _create_mcp_tools(self, repo_path: Path, is_local: bool = False) -> tuple[list[Any], list[Any]]: - """Create DSPy tools from MCP servers. - - Args: - repo_path: Path to the repository root - is_local: If True, skip git server (repo already on disk, no cloning needed) - """ - tools: list[Any] = [] - contexts: list[Any] = [] - tools_dir = Path(__file__).parent.parent.parent.parent / "tools" - repo_path_str = str(repo_path) - caller = "scope_identifier" - tools.extend(await connect_mcp_server(tools_dir / "filesystem" / "server.py", [repo_path_str], contexts, caller)) - tools.extend(await connect_mcp_server(tools_dir / "parsers" / "ripgrep" / "server.py", [repo_path_str], contexts, caller)) - tools.extend(await connect_mcp_server(tools_dir / "parsers" / "treesitter" / "server.py", [repo_path_str], contexts, caller)) - if not is_local: - tools.extend(await connect_mcp_server(tools_dir / "git" / "server.py", [], contexts, caller)) - return tools, contexts - - async def aforward(self, mr: MergeRequest, repo_path: Path, is_local: bool = False) -> list[ScopeResult]: - """Identify scopes in the repository for the given MR. - - Args: - mr: The merge request to analyze - repo_path: Path to the repository root - is_local: If True, repo is already on disk (skip cloning) - """ - # Get excluded directories from settings - excluded_dirs = self._settings.excluded_directories - - # Filter out binary, lock files, minified files, excluded directories, etc. - reviewable_files = [f for f in mr.changed_files if should_review_file(f, excluded_dirs)] - excluded_count = len(mr.changed_files) - len(reviewable_files) - if excluded_count > 0: - excluded_files = [f.filename for f in mr.changed_files if not should_review_file(f, excluded_dirs)] - logger.info(f"Excluded {excluded_count} non-reviewable files: {excluded_files[:10]}{'...' if len(excluded_files) > 10 else ''}") - - if not reviewable_files: - logger.warning("No reviewable files in MR - all files are binary, lock files, or in excluded directories") - return [] - - # Check if signature is enabled - if not self._settings.is_signature_enabled("scope"): - logger.warning("scope is disabled - using fallback single scope") - return [ScopeResult( - subroot=".", - scope_type=ScopeType.APPLICATION, - has_changes=True, - is_dependency=False, - confidence=0.5, - language=None, - package_manifest=None, - changed_files=reviewable_files, - reason="Scope identification disabled - fallback to single scope", - )] - tools, contexts = await self._create_mcp_tools(repo_path, is_local=is_local) - changed_file_paths = [f.filename for f in reviewable_files] - # Build map from filename to ChangedFile for post-processing - changed_files_map: dict[str, ChangedFile] = {f.filename: f for f in reviewable_files} - try: - # Get per-signature config - max_iters = self._settings.get_max_iters("scope") - temperature = self._settings.get_temperature("scope") - max_reasoning = self._settings.get_max_reasoning_tokens("scope") - - # Create ReAct agent - agent = dspy.ReAct( - signature=ScopeIdentifierSignature, - tools=tools, - max_iters=max_iters, - ) - logger.info(f"Identifying scopes for {len(changed_file_paths)} changed files...") - # Track scope signature costs - async with SignatureContext("scope", self._cost_tracker): - result = await agent.acall( - changed_files=changed_file_paths, - repo_owner=mr.repo_owner, - repo_name=mr.repo_name, - head_sha=mr.head_sha, - target_repo_path=str(repo_path), - mr_title=mr.title or "No title", - mr_description=mr.body or "No description", - is_local=is_local, - ) - scope_assignments: list[ScopeAssignment] = result.scopes - # Ensure we got valid scopes - if not scope_assignments: - raise ValueError("No scopes returned by agent") - # Convert ScopeAssignment (with string paths) to ScopeResult (with ChangedFile objects) - scopes = self._convert_assignments_to_results(scope_assignments, changed_files_map) - except Exception as e: - logger.error(f"Agent failed: {e}") - scopes = [ScopeResult( - subroot=".", - scope_type=ScopeType.APPLICATION, - has_changes=True, - is_dependency=False, - confidence=0.5, - language=None, - package_manifest=None, - changed_files=list(mr.changed_files), - reason=f"Fallback due to agent error: {e}", - )] - finally: - await cleanup_mcp_contexts(contexts) - # Log results - total_files = sum(len(s.changed_files) for s in scopes) - logger.info(f"Identified {len(scopes)} scopes covering {total_files} files") - return scopes - - def _convert_assignments_to_results( - self, - assignments: list[ScopeAssignment], - changed_files_map: dict[str, ChangedFile] - ) -> list[ScopeResult]: - """Convert LLM scope assignments to ScopeResults with proper ChangedFile objects. - - Args: - assignments: Scope assignments from LLM with string file paths - changed_files_map: Map from filename to ChangedFile object - - Returns: - List of ScopeResult with ChangedFile objects instead of strings - """ - results: list[ScopeResult] = [] - for assignment in assignments: - # Map string paths to ChangedFile objects - changed_files: list[ChangedFile] = [] - for filepath in assignment.changed_files: - if filepath in changed_files_map: - changed_files.append(changed_files_map[filepath]) - else: - logger.warning(f"File '{filepath}' from scope assignment not found in PR changed files") - results.append(ScopeResult( - subroot=assignment.subroot, - scope_type=assignment.scope_type, - has_changes=assignment.has_changes, - is_dependency=assignment.is_dependency, - confidence=assignment.confidence, - language=assignment.language, - package_manifest=assignment.package_manifest, - changed_files=changed_files, - reason=assignment.reason, - )) - return results - - def forward(self, mr: MergeRequest, repo_path: Path, is_local: bool = False) -> list[ScopeResult]: - """Identify scopes (sync wrapper).""" - return asyncio.run(self.aforward(mr, repo_path, is_local=is_local)) diff --git a/src/codespy/agents/reviewer/modules/scope_resolver.py b/src/codespy/agents/reviewer/modules/scope_resolver.py new file mode 100644 index 0000000..ad0fbd8 --- /dev/null +++ b/src/codespy/agents/reviewer/modules/scope_resolver.py @@ -0,0 +1,1220 @@ +"""Scope resolver module - merged deterministic analysis + ReAct agent refinement. + +This module combines deterministic scope identification with a ReAct agent +for intelligent refinement. The agent uses filesystem and search tools to +explore the codebase and make informed scope decisions, replacing the +previous ChainOfThought predictor that relied on a static repo tree. +""" + +from __future__ import annotations + +import asyncio +import fnmatch +import logging +import os +from pathlib import Path +from typing import Any + +import dspy # type: ignore[import-untyped] +from pydantic import BaseModel, Field + +from codespy.agents import SignatureContext, get_cost_tracker +from codespy.agents.context_safe import ContextSafe +from codespy.agents.memory.hippocampus import ContextMemory, Hippocampus +from codespy.agents.reviewer.models import ( + PackageManifest, + ReviewContext, + ScopeResult, + ScopeType, +) +from codespy.agents.reviewer.modules.manifest_parser import ( + PACKAGE_MANAGER_TO_ECOSYSTEM, + extract_dependencies, + extract_package_name, + infer_repo_from_name, +) +from codespy.config import get_settings +from codespy.config_memory import get_memory_store +from codespy.tools.git.client import get_client +from codespy.tools.git.models import ChangedFile, PullRequest, should_review_file +from codespy.tools.mcp_utils import cleanup_mcp_contexts, connect_mcp_server + +logger = logging.getLogger(__name__) + +# Files to read at each directory level +SKILL_FILES: list[str] = ["AGENTS.md", "CLAUDE.md", "SKILL.md"] + +# Subdirectories to scan for .md files (limited depth) +SKILL_DIRS: list[str] = [".kilo/agent", ".claude", ".agent", ".ai", ".cursor", ".codex"] + + +def collect_skills(repo_path: Path, subroot: str) -> str | None: + """Collect hierarchical skills from root down to scope subroot. + + Reads instruction files at each ancestor directory level. + Returns concatenated content (root-first) or None if nothing found. + """ + # Build path hierarchy: [".", "packages", "packages/auth"] + levels: list[str] = ["."] + if subroot and subroot != ".": + parts = subroot.split("/") + for i in range(1, len(parts) + 1): + levels.append("/".join(parts[:i])) + + sections: list[str] = [] + + for level in levels: + level_path = repo_path if level == "." else repo_path / level + + # Read standalone skill files + for filename in SKILL_FILES: + filepath = level_path / filename + if filepath.is_file(): + content = filepath.read_text(errors="ignore").strip() + if content: + header = filename if level == "." else f"{level}/{filename}" + sections.append(f"=== {header} ===\n{content}") + + # Read .md files from skill directories + for skill_dir in SKILL_DIRS: + dir_path = level_path / skill_dir + if dir_path.is_dir(): + for md_file in sorted(dir_path.glob("*.md")): + content = md_file.read_text(errors="ignore").strip() + if content: + rel = f"{level}/{skill_dir}/{md_file.name}" if level != "." else f"{skill_dir}/{md_file.name}" + sections.append(f"=== {rel} ===\n{content}") + + return "\n\n".join(sections) if sections else None + + +def _deepest_common_folder(scopes: list[ScopeResult], repo_slug: str) -> str: + """Compute the deepest common ancestor directory across all scope subroots. + + Args: + scopes: List of scope results + repo_slug: Repository slug for fallback path + + Returns: + Deepest common ancestor path (e.g., "/repo/scope/subroot/") + """ + subroots = [s.subroot for s in scopes] + if not subroots or any(sr in (".", "") for sr in subroots): + return f"/{repo_slug}/" + try: + common = os.path.commonpath(subroots) + except ValueError: + common = "" + if not common or common == ".": + return f"/{repo_slug}/" + return f"/{repo_slug}/{common.strip('/')}/" + +# Exact filename matches -> package manager +MANIFEST_FILES: dict[str, str] = { + # Go + "go.mod": "go", + # JavaScript/TypeScript + "package.json": "npm", + # Python + "pyproject.toml": "pip", + "setup.py": "pip", + "setup.cfg": "pip", + "Pipfile": "pip", + # Rust + "Cargo.toml": "cargo", + # Java/Kotlin/Scala + "pom.xml": "maven", + "build.gradle": "gradle", + "build.gradle.kts": "gradle", + "build.sbt": "sbt", + # PHP + "composer.json": "composer", + # Ruby + "Gemfile": "bundler", + # .NET / C# / F# + "Directory.Build.props": "dotnet", + "Directory.Packages.props": "dotnet", + # Swift + "Package.swift": "swift", + # Dart/Flutter + "pubspec.yaml": "pub", + # Elixir + "mix.exs": "mix", + # Clojure + "deps.edn": "clojure", + "project.clj": "leiningen", + # Haskell + "stack.yaml": "stack", + "cabal.project": "cabal", + # OCaml + "dune-project": "dune", + # Zig + "build.zig.zon": "zig", + # Perl + "cpanfile": "cpan", + "Makefile.PL": "cpan", + # R + "DESCRIPTION": "r", + # Helm charts + "Chart.yaml": "helm", +} + +# Glob patterns for manifests that use variable filenames +MANIFEST_GLOBS: dict[str, str] = { + "*.csproj": "dotnet", + "*.fsproj": "dotnet", + "*.vbproj": "dotnet", + "*.sln": "dotnet", + "*.cabal": "cabal", +} + +# Lock file -> manifest mapping +LOCK_TO_MANIFEST: dict[str, str] = { + "go.sum": "go.mod", + "package-lock.json": "package.json", + "yarn.lock": "package.json", + "pnpm-lock.yaml": "package.json", + "poetry.lock": "pyproject.toml", + "uv.lock": "pyproject.toml", + "Pipfile.lock": "Pipfile", + "Cargo.lock": "Cargo.toml", + "Gemfile.lock": "Gemfile", + "composer.lock": "composer.json", + "pubspec.lock": "pubspec.yaml", + "mix.lock": "mix.exs", + "packages.lock.json": "*.csproj", + "paket.lock": "paket.dependencies", +} + + +# Scope indicator directories +SCOPE_INDICATORS: dict[ScopeType, list[str]] = { + ScopeType.LIBRARY: [ + "lib/", "libs/", "libraries/", + "pkg/", "packages/", + "shared/", "common/", "core/", + "modules/", "mod/", + "sdk/", "sdks/", + "components/", + "plugins/", "extensions/", "addons/", + "framework/", + ], + ScopeType.SERVICE: [ + "services/", "service/", "svc/", + "microservices/", + "api/", "apis/", + "server/", "servers/", + "backend/", "backends/", + "gateway/", "gateways/", + "proxy/", "proxies/", + "workers/", "worker/", + "jobs/", "cron/", "schedulers/", + "consumers/", "producers/", + "functions/", "lambdas/", "lambda/", + "cmd/", + ], + ScopeType.APPLICATION: [ + "apps/", "app/", "applications/", + "web/", "www/", + "frontend/", "frontends/", + "client/", "clients/", + "ui/", + "dashboard/", "admin/", + "portal/", "console/", + "mobile/", "native/", + "ios/", "android/", + "electron/", "desktop/", + "site/", "website/", + ], + ScopeType.SCRIPT: [ + "scripts/", "script/", + "bin/", + "tools/", "tooling/", + "hack/", + "make/", + "ci/", "cd/", + ".github/", ".gitlab/", + ".circleci/", ".buildkite/", + ".azure-pipelines/", + "infra/", "infrastructure/", + "terraform/", "tf/", + "pulumi/", + "ansible/", "salt/", + "cloudformation/", + "docker/", + "k8s/", "kubernetes/", + "helm/", "charts/", + "kustomize/", + "deploy/", "deployment/", "deployments/", + "ops/", "devops/", "platform/", + "provisioning/", + "config/", "configs/", "configuration/", + ], +} + +# Flattened set for sparse path derivation +SCOPE_INDICATOR_DIRS = frozenset( + d.rstrip("/") for dirs in SCOPE_INDICATORS.values() for d in dirs +) + + +class ScopeBoundary(BaseModel): + """LLM output: a scope boundary decision (no file listing).""" + + subroot: str = Field(description="Path relative to repo root (e.g., 'packages/auth' or '.' for root)") + scope_type: ScopeType = Field(description="Type of scope") + reason: str = Field(description="Brief explanation for this boundary") + description: str = Field( + default="", + description="Brief description (max 500 chars) of what this scope/folder contains and its role in the project" + ) + + +class ScopeRefinementSignature(dspy.Signature): + """Refine and finalize scope boundaries for a pull request. + + You receive deterministic scope candidates (heuristic proposals), unassigned files, + and project instructions that describe the repository structure and conventions. + You have tools to explore the repository filesystem and search code. + + TOOLS AVAILABLE: + - list_directory: see directory contents + - get_tree: get subtree structure (use sparingly, targeted) + - read_file: read manifest files to understand package boundaries + - search_literal: find patterns across the codebase + - find_imports_of: understand dependencies between directories + + YOUR ROLE: + Produce the MINIMAL correct set of scope boundaries. The deterministic heuristics provide + a starting point — validate, merge, or reclassify as needed. + + Files are assigned automatically to the deepest matching boundary by path prefix. + You only need to decide WHERE boundaries are, not which files go where. + + REFINEMENT OPERATIONS: + 1. MERGE: Combine candidates that share a deployment/release boundary + 2. RECLASSIFY: Change scope_type if the heuristic got it wrong + 3. CREATE: New boundary only for clearly separate units (especially orphans) + 4. DROP: Remove candidates with no structural value + + WHEN TO USE TOOLS: + - Use list_directory or get_tree to verify a directory boundary exists + - Use read_file on manifest files to check if two directories share a package + - Use find_imports_of to check if directories are coupled (merge signal) + - Do NOT explore exhaustively — only when a decision requires verification + + CRITICAL RULES: + 1. Output only scope boundaries (subroots). Files are assigned automatically by path + prefix to the deepest matching scope. + 2. No overlapping scopes (parent contains child) + 3. Manifest-backed candidates (marked 'manifest=...') are authoritative. Keep them + unless merging multiple manifests into one. + 4. Prefer FEWER scopes. 1-3 scopes is typical for most PRs. + 5. When in doubt, MERGE into fewer scopes rather than split. + + OUTPUT: Final refined scope boundaries. Files are assigned automatically. + + For each scope boundary, include a `description` (max 615 tokens) summarizing + what the folder contains and its role in the project. For example: + - "Auth library handling JWT issuance and session management" + - "API gateway routing to downstream services" + """ + + candidates: str = dspy.InputField( + desc="Deterministic scope candidates with manifest info and file lists. Subject to refinement." + ) + orphan_files: list[str] = dspy.InputField( + desc="Changed files not assigned to any candidate (may be empty list)" + ) + pr_title: str = dspy.InputField(desc="PR title for intent context") + pr_description: str = dspy.InputField(desc="PR description for intent context") + project_instructions: str = dspy.InputField( + desc="Project coding guidelines and structure context from config files (AGENTS.md, .kilo/, etc.). May be empty." + ) + + scopes: list[ScopeBoundary] = dspy.OutputField( + desc="Scope boundaries (subroots) with descriptions. Files are assigned automatically — do NOT list files. " + "For each scope boundary, include a `description` (max 500 characters) summarizing what the folder contains and its role in the project." + ) + + +def derive_sparse_paths(changed_files: list[str]) -> list[str]: + """Derive minimal sparse checkout paths from changed files. + + Args: + changed_files: List of changed file paths + + Returns: + List of sparse paths for git sparse-checkout + """ + scope_roots: set[str] = set() + + for filepath in changed_files: + parts = filepath.split("/") + if len(parts) <= 1: + continue # Root-level file, handled by "/*" below + + # Strategy A: Find scope indicator and take the next directory + found_indicator = False + for i, part in enumerate(parts[:-1]): # Skip filename + if part.lower() in SCOPE_INDICATOR_DIRS and i + 1 < len(parts) - 1: + scope_root = "/".join(parts[:i + 2]) + "/" + scope_roots.add(scope_root) + found_indicator = True + break + + # Strategy B: No indicator found -- use depth-2 prefix + if not found_indicator: + depth = min(2, len(parts) - 1) + scope_roots.add("/".join(parts[:depth]) + "/") + + # Always include root-level files for root manifests + paths = sorted(scope_roots) + paths.append("/*") + + # Explicitly add root manifest files to ensure they are checked out + # in sparse/treeless clones (/* pattern doesn't always work reliably) + for manifest in MANIFEST_FILES: + paths.append(manifest) + for manifest_pattern in MANIFEST_GLOBS: + # For glob patterns like *.csproj, we need to add the pattern itself + paths.append(manifest_pattern) + + # Agent config directories — project instructions for ReAct agents + paths.extend([ + ".claude/", + ".kilo/", + ".agent/", + ".ai/", + ".cursor/", + ".codex/", + "AGENTS.md", + "CLAUDE.md", + "SKILL.md", + ]) + + return paths + + +class ScopeResolver(dspy.Module): + """Deterministic scope resolver with LLM fallback for ambiguous cases.""" + + def __init__(self) -> None: + """Initialize the scope resolver.""" + super().__init__() + self._cost_tracker = get_cost_tracker() + self._settings = get_settings() + + async def _create_tools(self, repo_path: Path) -> tuple[list[Any], list[Any]]: + """Create tools for the scope agent: filesystem + ripgrep. + + Args: + repo_path: Path to the repository root (not scope-restricted) + + Returns: + Tuple of (tools list, contexts list for cleanup) + """ + tools: list[Any] = [] + contexts: list[Any] = [] + tools_dir = Path(__file__).parent.parent.parent.parent / "tools" + repo_path_str = str(repo_path) + caller = "scope_resolver" + + tools.extend(await connect_mcp_server( + tools_dir / "storage" / "filesystem" / "server.py", + [repo_path_str], contexts, caller, + )) + tools.extend(await connect_mcp_server( + tools_dir / "parsers" / "ripgrep" / "server.py", + [repo_path_str], contexts, caller, + )) + return tools, contexts + + async def _ensure_repo( + self, pr: PullRequest, repo_path: Path, is_local: bool + ) -> None: + """Clone repo programmatically if not already on disk. + + Args: + pr: The pull request + repo_path: Path where repo should be cloned + is_local: If True, skip cloning (repo already on disk) + """ + if is_local: + logger.debug("Local review - skipping clone") + return + + if repo_path.exists() and (repo_path / ".git").exists(): + from git import Repo + logger.debug("Updating existing clone at %s", repo_path) + changed_file_paths = [f.filename for f in pr.changed_files] + sparse_paths = derive_sparse_paths(changed_file_paths) + # Update sparse-checkout config + sparse_file = repo_path / ".git" / "info" / "sparse-checkout" + sparse_file.parent.mkdir(parents=True, exist_ok=True) + sparse_file.write_text("\n".join(sparse_paths) + "\n") + # Fetch and checkout correct ref + repo = Repo(repo_path) + repo.git.fetch("origin", pr.head_sha, "--depth", "1") + repo.git.checkout(pr.head_sha) + # Ensure manifests at root + parent dirs + await self._ensure_manifests(repo_path, changed_file_paths) + return + + changed_file_paths = [f.filename for f in pr.changed_files] + sparse_paths = derive_sparse_paths(changed_file_paths) + logger.info("Sparse checkout paths: %s", sparse_paths) + + # Build a dummy URL to get the right client + if pr.platform == "gitlab": + gitlab_url = self._settings.gitlab_url.rstrip("/") + dummy_url = f"{gitlab_url}/{pr.repo_owner}/{pr.repo_name}/-/merge_requests/1" + else: + dummy_url = f"https://github.com/{pr.repo_owner}/{pr.repo_name}/pull/1" + + client = get_client(dummy_url, self._settings) + logger.info( + "Cloning %s/%s@%s...", pr.repo_owner, pr.repo_name, pr.head_sha[:8] + ) + + client.clone_repository( + owner=pr.repo_owner, + repo_name=pr.repo_name, + ref=pr.head_sha, + target_path=repo_path, + depth=1, + sparse_paths=sparse_paths, + ) + logger.info("Clone complete: %s", repo_path) + + # Ensure manifest files are present at root and parent directories + await self._ensure_manifests(repo_path, changed_file_paths) + + async def _ensure_manifests(self, repo_path: Path, changed_files: list[str]) -> None: + """Ensure manifest files at root and parent directories are checked out. + + Sparse/treeless clones may not materialize manifests at ancestor directories. + This explicitly checks out known manifest files at: + - Repository root + - Every ancestor directory of every changed file path + + Args: + repo_path: Path to the repository root + changed_files: List of changed file paths + """ + from git import Repo + + # Collect all ancestor directories of changed files + parent_dirs: set[str] = set() + for filepath in changed_files: + parts = filepath.split("/") + for depth in range(1, len(parts)): # skip filename, collect dirs + parent_dirs.add("/".join(parts[:depth])) + + # Build list of manifest paths to check + manifest_paths: list[str] = [] + + # Root manifests + for manifest in MANIFEST_FILES: + manifest_paths.append(manifest) + + # Parent manifests + for parent in parent_dirs: + for manifest in MANIFEST_FILES: + manifest_paths.append(f"{parent}/{manifest}") + + # Checkout missing manifests + try: + from git import Repo + from git.exc import GitCommandError + + repo = Repo(repo_path) + for path in manifest_paths: + if not (repo_path / path).exists(): + try: + repo.git.checkout("HEAD", "--", path) + logger.debug("Checked out manifest: %s", path) + except GitCommandError: + pass # File doesn't exist in repo — expected + except Exception as e: + logger.warning("Unexpected error checking out manifest %s: %s", path, e) + except Exception as e: + logger.warning("Failed to ensure manifests: %s", e) + + def _resolve( + self, repo_path: Path, changed_files: list[ChangedFile], repo: str + ) -> tuple[list[ScopeResult], list[ChangedFile]]: + """Run deterministic scope resolution. + + Args: + repo_path: Path to the cloned repository + changed_files: List of changed files + repo: Repo identifier + + Returns: + Tuple of (active scopes, orphan files) + """ + excluded_dirs = self._settings.excluded_directories + manifests = self._discover_manifests(repo_path, excluded_dirs) + logger.info( + "Manifest discovery at %s found %d manifest(s): %s", + repo_path, + len(manifests), + {str(k): v[1] for k, v in manifests.items()}, + ) + + # Build ScopeResult per manifest + scopes: dict[str, ScopeResult] = {} + for manifest_dir, (pkg_mgr, manifest_filename) in manifests.items(): + subroot = str(manifest_dir) if str(manifest_dir) != "." else "." + lock_file = self._find_lock_file(repo_path, manifest_dir, manifest_filename) + scope_type = self._classify_scope_type(subroot, manifest_filename) + changed_paths = {f.filename for f in changed_files} + deps_changed = self._dependencies_changed(manifest_dir, manifest_filename, lock_file, changed_paths) + manifest_path = str(manifest_dir / manifest_filename) if manifest_dir != Path(".") else manifest_filename + + # Extract package name from manifest + package_name = extract_package_name(manifest_path, repo_path) + + # Build deterministic description + description = f"{pkg_mgr} package at {subroot}" if subroot != "." else f"{pkg_mgr} package (root)" + + scopes[subroot] = ScopeResult( + repo=repo, + subroot=subroot, + scope_type=scope_type, + package_manifest=PackageManifest( + manifest_path=manifest_path, + lock_file_path=str(lock_file) if lock_file else None, + package_manager=pkg_mgr, + dependencies_changed=deps_changed, + package_name=package_name, + ), + reason=f"manifest {manifest_filename} at {subroot}/", + description=description, + ) + + has_nested_manifests = any(subroot != "." for subroot in scopes) + root_is_sole_manifest = "." in scopes and not has_nested_manifests + + for file in changed_files: + covered_by_nested = any( + subroot != "." and file.filename.startswith(subroot + "/") + for subroot in scopes + ) + if covered_by_nested or root_is_sole_manifest: + continue + + indicator_type, indicator_path = self._find_scope_indicator(file.filename) + if indicator_path and indicator_type and indicator_path not in scopes: + # Build deterministic description for indicator-based scope + description = f"{indicator_type.value} scope at {indicator_path}" + scopes[indicator_path] = ScopeResult( + repo=repo, + subroot=indicator_path, + scope_type=indicator_type, + reason="scope indicator in path (no parent manifest)", + description=description, + ) + + # Assign files to deepest matching scope + orphans = self._assign_files(list(scopes.values()), changed_files) + + # Return only scopes that have files + active_scopes = [s for s in scopes.values() if s.changed_files] + return active_scopes, orphans + + def _discover_manifests( + self, repo_path: Path, excluded_dirs: list[str] + ) -> dict[Path, tuple[str, str]]: + """Discover all package manifests in the repo. + + Args: + repo_path: Path to the repository root + excluded_dirs: List of directory names to exclude from scanning + + Returns: + Dict mapping manifest directory -> (package manager, filename) + """ + logger.debug("Walking %s for manifests (excluded: %s)", repo_path, excluded_dirs) + manifests: dict[Path, tuple[str, str]] = {} + excluded_set = set(excluded_dirs) + + for root, dirs, files in os.walk(repo_path): + # Skip excluded and hidden directories + dirs[:] = [d for d in dirs if d not in excluded_set and not d.startswith(".")] + + for filename in files: + # Check exact matches + if filename in MANIFEST_FILES: + manifest_path = Path(root) / filename + rel_path = manifest_path.relative_to(repo_path) + manifests[rel_path.parent] = (MANIFEST_FILES[filename], filename) + continue + + # Check glob patterns + for pattern, pkg_mgr in MANIFEST_GLOBS.items(): + if fnmatch.fnmatch(filename, pattern): + manifest_path = Path(root) / filename + rel_path = manifest_path.relative_to(repo_path) + manifests[rel_path.parent] = (pkg_mgr, filename) + break + + return manifests + + def _classify_scope_type(self, subroot: str, manifest_filename: str | None) -> ScopeType: + """Classify scope type from path indicators. + + Args: + subroot: Scope root path + manifest_filename: Manifest filename (optional) + + Returns: + ScopeType classification + """ + subroot_lower = subroot.lower() + + # Check for script indicators first (most specific) + for indicator in SCOPE_INDICATORS[ScopeType.SCRIPT]: + if indicator.rstrip("/") in subroot_lower.split("/"): + return ScopeType.SCRIPT + + # Check for service indicators + for indicator in SCOPE_INDICATORS[ScopeType.SERVICE]: + if indicator.rstrip("/") in subroot_lower.split("/"): + return ScopeType.SERVICE + + # Check for application indicators + for indicator in SCOPE_INDICATORS[ScopeType.APPLICATION]: + if indicator.rstrip("/") in subroot_lower.split("/"): + return ScopeType.APPLICATION + + # Check for library indicators + for indicator in SCOPE_INDICATORS[ScopeType.LIBRARY]: + if indicator.rstrip("/") in subroot_lower.split("/"): + return ScopeType.LIBRARY + + # Default based on manifest type + if manifest_filename: + if manifest_filename == "Chart.yaml": + return ScopeType.SERVICE # Helm charts are deployable + if manifest_filename in ("Dockerfile", "docker-compose.yml"): + return ScopeType.SCRIPT + + # Fallback: library if it has a manifest, application otherwise + return ScopeType.LIBRARY if manifest_filename else ScopeType.APPLICATION + + def _find_lock_file( + self, repo_path: Path, manifest_dir: Path, manifest_filename: str + ) -> Path | None: + """Find the lock file corresponding to a manifest. + + Args: + repo_path: Path to the repository root + manifest_dir: Directory containing manifest + manifest_filename: Name of manifest file + + Returns: + Path to lock file or None + """ + search_dir = repo_path / manifest_dir + if not search_dir.exists(): + return None + + for lock_file, manifest_pattern in LOCK_TO_MANIFEST.items(): + if fnmatch.fnmatch(manifest_filename, manifest_pattern): + lock_path = search_dir / lock_file + if lock_path.exists(): + return lock_path.relative_to(repo_path) + + return None + + def _dependencies_changed( + self, + manifest_dir: Path, + manifest_filename: str, + lock_file: Path | None, + changed_paths: set[str], + ) -> bool: + """Check if manifest or lock file was changed. + + Args: + manifest_dir: Directory containing manifest + manifest_filename: Name of manifest file + lock_file: Path to lock file (optional) + changed_paths: Set of changed file paths + + Returns: + True if dependencies changed + """ + manifest_path = str(manifest_dir / manifest_filename) if manifest_dir != Path(".") else manifest_filename + if manifest_path in changed_paths: + return True + return bool(lock_file and str(lock_file) in changed_paths) + + def _assign_files( + self, scopes: list[ScopeResult], changed_files: list[ChangedFile] + ) -> list[ChangedFile]: + """Assign each changed file to its deepest matching scope. + + Args: + scopes: List of scope results + changed_files: Changed files to assign + + Returns: + List of orphan files that couldn't be assigned + """ + # Sort by depth (deepest first) for greedy assignment + sorted_scopes = sorted(scopes, key=lambda s: (-s.subroot.count("/"), s.subroot)) + orphans: list[ChangedFile] = [] + + for file in changed_files: + assigned = False + for scope in sorted_scopes: + prefix = scope.subroot + "/" if scope.subroot != "." else "" + if file.filename.startswith(prefix) or (scope.subroot == "." and "/" not in file.filename): + scope.changed_files.append(file) + scope.has_changes = True + assigned = True + break + if not assigned: + orphans.append(file) + + return orphans + + def _find_scope_indicator(self, filepath: str) -> tuple[ScopeType | None, str]: + """Find scope indicator in file path. + + Args: + filepath: File path to analyze + + Returns: + Tuple of (scope type, scope root path) or (None, "") + """ + parts = filepath.split("/") + + for i, part in enumerate(parts[:-1]): # Skip filename + part_lower = part.lower() + + # Check each scope type + for scope_type, indicators in SCOPE_INDICATORS.items(): + for indicator in indicators: + indicator_name = indicator.rstrip("/") + if part_lower == indicator_name: + # Scope root is one level past the indicator + if i + 1 < len(parts) - 1: + scope_root = "/".join(parts[:i + 2]) + return scope_type, scope_root + + return None, "" + + def _format_candidate(self, s: ScopeResult) -> str: + """Format a scope candidate for the LLM. + + Args: + s: Scope result to format + + Returns: + Formatted candidate string + """ + manifest_info = "" + if s.package_manifest: + manifest_info = f", manifest={s.package_manifest.manifest_path}" + files = ", ".join(f.filename for f in s.changed_files) + return f"- {s.subroot} ({s.scope_type.value}{manifest_info}): files=[{files}]" + + def _apply_boundaries( + self, + boundaries: list[ScopeBoundary], + all_changed_files: list[ChangedFile], + deterministic_scopes: list[ScopeResult], + repo: str, + ) -> list[ScopeResult]: + """Apply LLM boundaries + manifest guardrail + deterministic file assignment. + + Args: + boundaries: Scope boundaries from LLM + all_changed_files: All changed files to assign + deterministic_scopes: Original deterministic scopes (for manifest info) + repo: Repo identifier + + Returns: + List of ScopeResult with files assigned + """ + # Index manifest-backed scopes from deterministic layer + manifest_scopes = { + s.subroot: s for s in deterministic_scopes if s.package_manifest + } + + # Build final boundary set + final_boundaries: dict[str, ScopeResult] = {} + + # 1. Always include manifest-backed scopes (immutable baseline) + for subroot, det_scope in manifest_scopes.items(): + final_boundaries[subroot] = ScopeResult( + repo=repo, + subroot=subroot, + scope_type=det_scope.scope_type, + package_manifest=det_scope.package_manifest, + reason=det_scope.reason, + ) + + # 2. Add LLM boundaries — but discard if child of a manifest scope + for boundary in boundaries: + # Check if this boundary is inside a manifest-backed scope + inside_manifest = any( + boundary.subroot.startswith(ms + "/") or ms == "." + for ms in manifest_scopes + if ms != boundary.subroot + ) + if inside_manifest: + logger.debug( + "Discarding LLM boundary '%s' — inside manifest scope", boundary.subroot + ) + continue + + # LLM can override scope_type of manifest scopes + if boundary.subroot in final_boundaries: + final_boundaries[boundary.subroot].scope_type = boundary.scope_type + else: + final_boundaries[boundary.subroot] = ScopeResult( + repo=repo, + subroot=boundary.subroot, + scope_type=boundary.scope_type, + reason=boundary.reason, + ) + + # 3. Deterministic file assignment to deepest matching boundary + orphans = self._assign_files(list(final_boundaries.values()), all_changed_files) + + # 4. Stragglers → root scope + if orphans: + if "." in final_boundaries: + for f in orphans: + final_boundaries["."].changed_files.append(f) + final_boundaries["."].has_changes = True + else: + final_boundaries["."] = ScopeResult( + repo=repo, subroot=".", scope_type=ScopeType.APPLICATION, + has_changes=True, changed_files=orphans, + reason="catch-all for unmatched files", + ) + + return [s for s in final_boundaries.values() if s.changed_files] + + async def _refine_scopes( + self, + scopes: list[ScopeResult], + orphans: list[ChangedFile], + review_context: ReviewContext, + ) -> tuple[list[ScopeResult], ContextMemory | None]: + """Use ReAct agent to refine scope assignments from deterministic candidates. + + Args: + scopes: Already-resolved scope results + orphans: Orphan files that couldn't be assigned + review_context: Review context with memory and metadata + + Returns: + Tuple of (list of ScopeResult with agent-resolved assignments, + ContextMemory with topics and items from Hippocampus) + """ + from codespy.agents.memory.hippocampus import ( + ContextMemory, + Topic, + compute_common_ancestor_topic_id, + make_topic_id, + ) + + # Local bindings from review_context metadata + pr = review_context.metadata.pr + repo_path = review_context.metadata.repo_path + run_id = review_context.metadata.run_id + + # Build candidates string from already-resolved scopes + candidates_str = "\n".join(self._format_candidate(s) for s in scopes) + + # Read root-level project instructions for the scope agent + project_instructions = collect_skills(repo_path, ".") or "" + + max_iters = self._settings.get_max_iters("scope") + tools, contexts = await self._create_tools(repo_path) + try: + agent = ContextSafe( + dspy.ReAct( + signature=ScopeRefinementSignature, + tools=tools, + max_iters=max_iters, + ), + ScopeRefinementSignature, + tools=tools, + name="scope", + ) + mem: Hippocampus | None = None + + async with SignatureContext("scope", self._cost_tracker): + if self._settings.get_memory_enabled("scope"): + question = ( + f"refine scopes of {review_context.pr_context.repo_slug}: " + f"PR #{review_context.pr_context.pr_number} " + f"{review_context.pr_context.pr_title}: " + f"{review_context.pr_context.summary}" + ) + mem = Hippocampus( + agent, + budget=self._settings.get_memory_budget("scope"), + max_reflects=self._settings.get_memory_max_reflects("scope"), + question=question, + task_name="scope", + run_id=run_id, + initial_memory=review_context.memory, + ) + result = await mem.aforward( + candidates=candidates_str, + orphan_files=[f.filename for f in orphans], + pr_title=pr.title or "No title", + pr_description=pr.body or "No description", + project_instructions=project_instructions, + ) + else: + result = await agent.acall( + candidates=candidates_str, + orphan_files=[f.filename for f in orphans], + pr_title=pr.title or "No title", + pr_description=pr.body or "No description", + project_instructions=project_instructions, + ) + + # Collect all changed files (from scopes + orphans) + all_files = [f for s in scopes for f in s.changed_files] + orphans + + # Apply LLM boundaries with manifest guardrail and deterministic file assignment + final_scopes = self._apply_boundaries( + result.scopes, all_files, scopes, pr.repo_slug + ) + + # Copy ScopeBoundary.description to ScopeResult.description (overrides deterministic fallback) + boundary_descriptions: dict[str, str] = {b.subroot: b.description for b in result.scopes} + for scope in final_scopes: + if scope.subroot in boundary_descriptions: + scope.description = boundary_descriptions[scope.subroot] + + # Build topic IDs + internal lookup + internal_packages: dict[str, str] = {} + scope_topic_ids: dict[str, str] = {} + for scope in final_scopes: + pkg_name = scope.package_manifest.package_name if scope.package_manifest else None + tid = make_topic_id(pr.repo_full_name, scope.subroot, pkg_name) + scope_topic_ids[scope.subroot] = tid + if pkg_name: + internal_packages[pkg_name] = tid + + # Build Topics with resolved dependencies + scope_topics: list[Topic] = [] + for scope in final_scopes: + dep_topic_ids: list[str] = [] + if scope.package_manifest: + dep_names, dep_repos = extract_dependencies( + scope.package_manifest.manifest_path, repo_path + ) + ecosystem = PACKAGE_MANAGER_TO_ECOSYSTEM.get( + scope.package_manifest.package_manager, + scope.package_manifest.package_manager, + ) + for name in dep_names: + if name in internal_packages: + # Rule 1: internal scope match + dep_topic_ids.append(internal_packages[name]) + elif name in dep_repos: + # Rule 2: repo identifiable from source metadata + dep_topic_ids.append(make_topic_id(dep_repos[name], "", name)) + elif infer_repo_from_name(name): + # Rule 2: repo identifiable from dep name (Go modules) + dep_topic_ids.append(make_topic_id(infer_repo_from_name(name), "", name)) + else: + # Rule 3: external + dep_topic_ids.append(f"{ecosystem}/{name}") + + scope_topics.append(Topic( + id=scope_topic_ids[scope.subroot], + description=scope.description, + dependencies=dep_topic_ids, + )) + + # Compute common ancestor topic if >1 scope + common_ancestor_topic_id = compute_common_ancestor_topic_id( + pr.repo_full_name, [s.subroot for s in final_scopes] + ) + if common_ancestor_topic_id: + # Build description: "Common context for scopes: subroot1, subroot2, ..." + subroot_list = ", ".join(s.subroot for s in final_scopes) + common_desc = f"Common context for scopes: {subroot_list}" + scope_topics.append(Topic(id=common_ancestor_topic_id, description=common_desc)) + stamp_topic_ids = [common_ancestor_topic_id] + elif scope_topics: + # Single scope: stamp with its topic ID + stamp_topic_ids = [scope_topics[0].id] + else: + stamp_topic_ids = [] + + # Attach hierarchical skills to each produced scope + for scope in final_scopes: + scope.skills = collect_skills(repo_path, scope.subroot) + + # Bind topics to hippocampus cmem BEFORE building context_memory. + # This ensures: (a) persisted episode includes topics, (b) items + # copied into context_memory are pre-stamped with topic_ids, + # (c) any new items from consolidation also get topic_ids via _topic_ids. + if mem is not None and stamp_topic_ids: + mem._topic_ids = stamp_topic_ids + mem.cmem.bind_topics(scope_topics, stamp_topic_ids) + + # Build ContextMemory from Hippocampus cmem (items already stamped) + context_memory = ContextMemory( + topics=scope_topics, + context_roadmap=mem.cmem.context_roadmap.copy() if mem else [], + context_understanding=mem.cmem.context_understanding.copy() if mem else [], + domain_constants=mem.cmem.domain_constants.copy() if mem else [], + parsing_schema=mem.cmem.parsing_schema.copy() if mem else [], + reusable_results=mem.cmem.reusable_results.copy() if mem else [], + ) + + # Persist episode at deepest common folder when memory is enabled + if mem is not None: + common_dir = _deepest_common_folder(final_scopes, pr.repo_slug) + scope_desc = "\n".join( + f"- {s.subroot} ({s.scope_type.value}): {len(s.changed_files)} files" + for s in final_scopes + ) + await mem.aend_episode( + get_memory_store(self._settings), + common_dir, + artifacts={"scopes": scope_desc}, + ) + return final_scopes, context_memory + + # No memory enabled: return scopes with ContextMemory containing topics only + if scope_topics: + return final_scopes, context_memory + return final_scopes, None + + finally: + await cleanup_mcp_contexts(contexts) + + async def aforward( + self, + review_context: ReviewContext, + ) -> tuple[list[ScopeResult], ContextMemory | None]: + """Resolve scopes in the repository for the given PR. + + Args: + review_context: Review context with inherited memory and metadata + + Returns: + Tuple of (list of ScopeResult, final context memory or None) + """ + # Local bindings from review_context metadata + pr = review_context.metadata.pr + repo_path = review_context.metadata.repo_path + is_local = review_context.metadata.is_local + run_id = review_context.metadata.run_id + + excluded_dirs = self._settings.excluded_directories + reviewable_files = [f for f in pr.changed_files if should_review_file(f, excluded_dirs)] + if not reviewable_files: + return [], review_context.memory + repo = pr.repo_slug + if not self._settings.is_signature_enabled("scope"): + fallback = ScopeResult( + repo=repo, subroot=".", scope_type=ScopeType.APPLICATION, + has_changes=True, changed_files=reviewable_files, + reason="Scope identification disabled", + description="Repository root", + ) + fallback.skills = collect_skills(repo_path, ".") + root_topic = fallback.topic(pr.repo_full_name) + return [fallback], ContextMemory(topics=[root_topic]) + + try: + await self._ensure_repo(pr, repo_path, is_local) + scopes, orphans = self._resolve(repo_path, reviewable_files, repo) + # Log deterministic scopes before LLM refinement + if scopes: + det_summary = "\n".join( + f" - {s.subroot} ({s.scope_type.value})" + f"{f', manifest={s.package_manifest.manifest_path}' if s.package_manifest else ''}" + f": {len(s.changed_files)} files" + for s in scopes + ) + logger.info( + "Deterministic scope identification found %d scope(s):\n%s", + len(scopes), det_summary + ) + if orphans: + logger.info("Deterministic identification produced %d orphan(s)", len(orphans)) + # Load prior scope memory from the deepest common folder + loaded_memory: ContextMemory | None = None + if self._settings.get_memory_enabled("scope"): + from codespy.agents.memory.hippocampus.episode import find_latest_episode + store = get_memory_store(self._settings) + common_dir = _deepest_common_folder(scopes, pr.repo_slug) if scopes else f"/{pr.repo_slug}/" + prior_episode = find_latest_episode(store, common_dir, task="scope", exclude_run_id=run_id) + # Fallback: prior run may have persisted at repo root if scopes differed + if prior_episode is None and common_dir != f"/{pr.repo_slug}/": + prior_episode = find_latest_episode( + store, f"/{pr.repo_slug}/", task="scope", exclude_run_id=run_id + ) + if prior_episode is not None: + loaded_memory = prior_episode.context_memory + # Strip prior-run topics: current run builds authoritative topics + # via bind_topics after refinement. Clearing item topic_ids ensures + # bind_topics can re-bind them to the current run's stamp_topic_ids. + loaded_memory.topics = [] + for item in loaded_memory.all_items(): + item.topic_ids = [] + logger.info( + "Loaded prior scope memory (run=%s, items=%d)", + prior_episode.run_id[:8], len(loaded_memory.all_items()), + ) + else: + logger.debug("No prior scope episode found at %s", common_dir) + # Inject loaded memory into review_context for _refine_scopes + if loaded_memory is not None: + merged_memory = ( + ContextMemory.merge(loaded_memory, review_context.memory) + if review_context.memory + else loaded_memory + ) + review_context = ReviewContext( + pr_context=review_context.pr_context, + memory=merged_memory, + metadata=review_context.metadata, + ) + scopes, context_memory = await self._refine_scopes( + scopes, orphans, review_context + ) + # Log final scopes for visibility + scope_summary = "\n".join( + f" - {s.subroot} ({s.scope_type.value}): {len(s.changed_files)} files" + for s in scopes + ) + logger.info("Resolved %d scope(s) for %s:\n%s", len(scopes), pr.repo_slug, scope_summary) + return scopes, context_memory + + except Exception as e: + logger.error("Scope resolution failed: %s", e, exc_info=True) + fallback = ScopeResult( + repo=repo, subroot=".", scope_type=ScopeType.APPLICATION, + has_changes=True, changed_files=reviewable_files, + reason=f"Fallback due to error: {e}", + description="Repository root", + ) + root_topic = fallback.topic(pr.repo_full_name) + return [fallback], ContextMemory(topics=[root_topic]) + + def forward( + self, + review_context: ReviewContext, + ) -> tuple[list[ScopeResult], ContextMemory | None]: + """Resolve scopes (sync wrapper).""" + return asyncio.run(self.aforward(review_context)) diff --git a/src/codespy/agents/reviewer/modules/summarizer.py b/src/codespy/agents/reviewer/modules/summarizer.py new file mode 100644 index 0000000..08abaef --- /dev/null +++ b/src/codespy/agents/reviewer/modules/summarizer.py @@ -0,0 +1,149 @@ +"""PR summarizer module — produces a concise summary before scope identification.""" + +import logging +from typing import TYPE_CHECKING + +import dspy + +from codespy.agents import SignatureContext, get_cost_tracker +from codespy.agents.context_safe import ContextSafe +from codespy.agents.memory.hippocampus import ContextMemory, Hippocampus +from codespy.agents.reviewer.modules.scope_resolver import _deepest_common_folder +from codespy.config import get_settings +from codespy.config_memory import get_memory_store + +if TYPE_CHECKING: + from codespy.agents.reviewer.models import ScopeResult + +logger = logging.getLogger(__name__) + + +class PRSummarySignature(dspy.Signature): + """Summarize what a pull request does in 2-3 sentences. + + You are a busy Principal Engineer. Be extremely terse. State facts only. + Based on the title, description, changed file paths, and code patches, + describe what this PR accomplishes. No polite filler. No conversational language. + """ + + pr_title: str = dspy.InputField(desc="Title of the pull request") + pr_description: str = dspy.InputField(desc="Description/body of the PR") + changed_file_paths: list[str] = dspy.InputField( + desc="List of changed file paths from the PR" + ) + patches: str = dspy.InputField( + desc="Unified diff patches showing code changes. Each patch is prefixed with the filename." + ) + + summary: str = dspy.OutputField( + desc="2-3 sentence summary of what this PR accomplishes" + ) + + +class Summarizer(dspy.Module): + """Produces a concise PR summary used as Hippocampus question for all downstream modules.""" + + def __init__(self) -> None: + super().__init__() + self._cost_tracker = get_cost_tracker() + self._settings = get_settings() + + def forward( + self, + pr_title: str, + pr_description: str, + pr_number: int, + changed_file_paths: list[str], + patches: str, + repo_slug: str, + run_id: str | None = None, + scopes: list["ScopeResult"] | None = None, + initial_memory: ContextMemory | None = None, + topic_ids: list[str] | None = None, + ) -> tuple[str, ContextMemory | None]: + """Generate a PR summary. + + Args: + pr_title: Title of the pull request + pr_description: Description/body of the PR + pr_number: PR number + changed_file_paths: List of changed file paths + patches: Unified diff patches showing code changes + repo_slug: Host-qualified repo slug for episode path + run_id: Pipeline run identifier + scopes: List of resolved scopes for per-scope episode persistence + initial_memory: Optional context memory from scope resolver + topic_ids: Optional list of topic IDs for auto-tagging + + Returns: + Tuple of (summary string, final context memory or None) + """ + + if not self._settings.is_signature_enabled("summary"): + logger.debug("Skipping summary: disabled") + return pr_title or "No title", initial_memory + # Load latest episode per scope and merge with inherited memory + if self._settings.get_memory_enabled("summary") and scopes: + from codespy.agents.memory.hippocampus.episode import find_latest_episode + store = get_memory_store(self._settings) + per_scope_memories: list[ContextMemory] = [] + for scope in scopes: + ep = find_latest_episode(store, scope.scope_path(), task=None, exclude_task="scope", exclude_run_id=run_id) + if ep is not None: + per_scope_memories.append(ep.context_memory) + if per_scope_memories: + all_memories = ([initial_memory] if initial_memory else []) + per_scope_memories + initial_memory = ContextMemory.merge(*all_memories) + logger.info( + "Merged %d prior scope episode(s) into summarizer memory", + len(per_scope_memories), + ) + summarizer = ContextSafe(dspy.ChainOfThought(PRSummarySignature), PRSummarySignature, name="summary") + logger.info("Generating PR summary...") + + question = f"summarize {repo_slug}: pull request {pr_number} {pr_title}" + + mem: Hippocampus | None = None + with SignatureContext("summary", self._cost_tracker): + if self._settings.get_memory_enabled("summary"): + mem = Hippocampus( + summarizer, + budget=self._settings.get_memory_budget("summary"), + max_reflects=self._settings.get_memory_max_reflects("summary"), + question=question, + task_name="summary", + run_id=run_id, + initial_memory=initial_memory, + topic_ids=topic_ids, + ) + result = mem( + pr_title=pr_title, + pr_description=pr_description, + changed_file_paths=changed_file_paths, + patches=patches, + ) + mem.end_episode( + get_memory_store(self._settings), + _deepest_common_folder(scopes, repo_slug) if scopes else f"/{repo_slug}/", + artifacts={"summary": result.summary}, + ) + else: + result = summarizer( + pr_title=pr_title, + pr_description=pr_description, + changed_file_paths=changed_file_paths, + patches=patches, + ) + + logger.info(f"PR summary: {result.summary[:80]}...") + # Return final context memory when memory is enabled + final_memory = mem.cmem.model_copy(deep=True) if mem else initial_memory + + # Persist episode at each scope location if memory is enabled and scopes are provided + if mem is not None and scopes: + store = get_memory_store(self._settings) + for scope in scopes: + path = mem.episode_file_path(scope.scope_path()) + mem.save_episode(store, path) + + return result.summary, final_memory diff --git a/src/codespy/agents/reviewer/modules/supply_chain_auditor.py b/src/codespy/agents/reviewer/modules/supply_chain_auditor.py index e86caee..a01cf94 100644 --- a/src/codespy/agents/reviewer/modules/supply_chain_auditor.py +++ b/src/codespy/agents/reviewer/modules/supply_chain_auditor.py @@ -2,15 +2,25 @@ import asyncio import logging +from collections.abc import Sequence from pathlib import Path -from typing import Any, Sequence +from typing import Any import dspy # type: ignore[import-untyped] from codespy.agents import SignatureContext, get_cost_tracker -from codespy.agents.reviewer.models import Issue, IssueCategory, ScopeResult -from codespy.agents.reviewer.modules.helpers import MIN_CONFIDENCE, resolve_scope_root, strip_prefix, restore_repo_paths +from codespy.agents.context_safe import ContextSafe +from codespy.agents.memory.hippocampus import ContextMemory, Hippocampus +from codespy.agents.reviewer.models import Issue, IssueCategory, ReviewContext, ScopeResult +from codespy.agents.reviewer.modules.helpers import ( + MIN_CONFIDENCE, + issues_to_markdown, + resolve_scope_root, + restore_repo_paths, + strip_prefix, +) from codespy.config import get_settings +from codespy.config_memory import get_memory_store from codespy.tools.mcp_utils import cleanup_mcp_contexts, connect_mcp_server logger = logging.getLogger(__name__) @@ -183,7 +193,7 @@ async def _create_scoped_tools( # Add filesystem tools for reading files and exploring structure tools.extend(await connect_mcp_server( - tools_dir / "filesystem" / "server.py", + tools_dir / "storage" / "filesystem" / "server.py", [scope_root_str], contexts, caller, @@ -227,7 +237,11 @@ async def _create_osv_tools(self) -> tuple[list[Any], list[Any]]: return tools, contexts - async def aforward(self, scopes: Sequence[ScopeResult], repo_path: Path) -> list[Issue]: + async def aforward( + self, + scopes: Sequence[ScopeResult], + review_context: ReviewContext, + ) -> tuple[list[Issue], ContextMemory | None]: """Analyze scopes for supply chain security vulnerabilities and return issues. For each scope, filesystem/parser tools are created rooted at @@ -237,22 +251,29 @@ async def aforward(self, scopes: Sequence[ScopeResult], repo_path: Path) -> list Args: scopes: The scopes containing changed files to analyze - repo_path: Path to the cloned repository for reading manifest files + review_context: ReviewContext containing PR identity, inherited memory, + and runtime pipeline metadata (repo_path, run_id, mr) Returns: - List of security issues found across all scopes + Tuple of (list of issues, merged context memory or None) """ + # Local bindings from review_context metadata + repo_path = review_context.metadata.repo_path + run_id = review_context.metadata.run_id + pr = review_context.metadata.pr + # Check if supply chain signature is enabled if not self._settings.is_signature_enabled("supply_chain"): logger.debug("Skipping supply_chain: disabled") - return [] + return [], review_context.memory # Check if any scope has supply-chain-relevant changes if not self._needs_analysis(scopes): logger.info("Skipping supply chain analysis: no dependency changes or Dockerfiles modified") - return [] + return [], review_context.memory all_issues: list[Issue] = [] + scope_memories: list[ContextMemory] = [] supply_chain_max_iters = self._settings.get_max_iters("supply_chain") # Create OSV tools once (shared across scopes, no filesystem root) @@ -294,10 +315,15 @@ async def aforward(self, scopes: Sequence[ScopeResult], repo_path: Path) -> list try: # Combine scoped filesystem tools with shared OSV tools all_tools = scoped_tools + osv_tools - supply_chain_agent = dspy.ReAct( - signature=SupplyChainSecuritySignature, + supply_chain_agent = ContextSafe( + dspy.ReAct( + signature=SupplyChainSecuritySignature, + tools=all_tools, + max_iters=supply_chain_max_iters, + ), + SupplyChainSecuritySignature, tools=all_tools, - max_iters=supply_chain_max_iters, + name="supply_chain", ) logger.debug( @@ -305,39 +331,88 @@ async def aforward(self, scopes: Sequence[ScopeResult], repo_path: Path) -> list f"manifest={bool(manifest_path)}" ) # Track supply_chain signature costs separately + mem: Hippocampus | None = None async with SignatureContext("supply_chain", self._cost_tracker): - result = await supply_chain_agent.acall( - manifest_path=manifest_path, - lock_file_path=lock_file_path, - package_manager=package_manager, - category=IssueCategory.SECURITY, - ) - issues = [ - issue for issue in result.issues - if issue.confidence >= MIN_CONFIDENCE - ] + if self._settings.get_memory_enabled("supply_chain"): + question = ( + f"review supply chain of {scope.repo}: {scope.subroot}: " + f"pull request {review_context.pr_context.pr_number} {review_context.pr_context.pr_title}: {review_context.pr_context.summary}" + ) + topic_ids = [scope.topic(pr.repo_full_name).id] if pr else [] + mem = Hippocampus( + supply_chain_agent, + budget=self._settings.get_memory_budget("supply_chain"), + max_reflects=self._settings.get_memory_max_reflects( + "supply_chain" + ), + question=question, + task_name="supply_chain", + run_id=run_id, + initial_memory=review_context.memory, + topic_ids=topic_ids, + ) + result = await mem.aforward( + manifest_path=manifest_path, + lock_file_path=lock_file_path, + package_manager=package_manager, + category=IssueCategory.SECURITY, + ) + issues = [ + issue for issue in result.issues + if issue.confidence >= MIN_CONFIDENCE + ] + await mem.aend_episode( + get_memory_store(self._settings), + scope.scope_path(), + artifacts={"review": issues_to_markdown(issues)}, + ) + # Collect scope's context memory + if mem: + scope_memories.append(mem.cmem.model_copy(deep=True)) + else: + result = await supply_chain_agent.acall( + manifest_path=manifest_path, + lock_file_path=lock_file_path, + package_manager=package_manager, + category=IssueCategory.SECURITY, + ) + issues = [ + issue for issue in result.issues + if issue.confidence >= MIN_CONFIDENCE + ] # Restore repo-root-relative paths in reported issues restore_repo_paths(issues, scope.subroot) all_issues.extend(issues) logger.debug(f" Supply chain security in scope {scope.subroot}: {len(issues)} issues") except Exception as e: - logger.error(f"Error analyzing supply chain in scope {scope.subroot}: {e}") + logger.error(f"Error analyzing supply chain in scope {scope.subroot}: {e}", exc_info=True) finally: await cleanup_mcp_contexts(scoped_contexts) finally: await cleanup_mcp_contexts(osv_contexts) logger.info(f"Security audit found {len(all_issues)} issues") - return all_issues - - def forward(self, scopes: Sequence[ScopeResult], repo_path: Path) -> list[Issue]: + # Merge all scope context memories into one module-level memory + merged_memory = ( + ContextMemory.merge(*scope_memories) + if scope_memories + else review_context.memory + ) + return all_issues, merged_memory + + def forward( + self, + scopes: Sequence[ScopeResult], + review_context: ReviewContext, + ) -> tuple[list[Issue], ContextMemory | None]: """Analyze scopes for supply chain security vulnerabilities (sync wrapper). Args: scopes: The scopes containing changed files to analyze - repo_path: Path to the cloned repository for reading manifest files + review_context: ReviewContext containing PR identity, inherited memory, + and runtime pipeline metadata (repo_path, run_id, mr) Returns: - List of security issues found across all scopes + Tuple of (list of issues, merged context memory or None) """ - return asyncio.run(self.aforward(scopes, repo_path)) \ No newline at end of file + return asyncio.run(self.aforward(scopes, review_context)) diff --git a/src/codespy/agents/reviewer/reporters/__init__.py b/src/codespy/agents/reviewer/reporters/__init__.py index 4db9ad5..f1eaf7b 100644 --- a/src/codespy/agents/reviewer/reporters/__init__.py +++ b/src/codespy/agents/reviewer/reporters/__init__.py @@ -8,4 +8,4 @@ "BaseReporter", "StdoutReporter", "GitReporter", -] \ No newline at end of file +] diff --git a/src/codespy/agents/reviewer/reporters/base.py b/src/codespy/agents/reviewer/reporters/base.py index 894ee36..0d3e449 100644 --- a/src/codespy/agents/reviewer/reporters/base.py +++ b/src/codespy/agents/reviewer/reporters/base.py @@ -15,4 +15,4 @@ def report(self, result: ReviewResult) -> None: Args: result: The review result to report. """ - pass \ No newline at end of file + pass diff --git a/src/codespy/agents/reviewer/reporters/git.py b/src/codespy/agents/reviewer/reporters/git.py index 21699b5..fb3a1a6 100644 --- a/src/codespy/agents/reviewer/reporters/git.py +++ b/src/codespy/agents/reviewer/reporters/git.py @@ -305,4 +305,4 @@ def _build_inline_comments(self, issues: list[Issue]) -> list[dict]: # Backward compatibility alias -GitHubPRReporter = GitReporter \ No newline at end of file +GitHubPRReporter = GitReporter diff --git a/src/codespy/agents/reviewer/reporters/stdout.py b/src/codespy/agents/reviewer/reporters/stdout.py index eba403b..e766512 100644 --- a/src/codespy/agents/reviewer/reporters/stdout.py +++ b/src/codespy/agents/reviewer/reporters/stdout.py @@ -35,4 +35,4 @@ def report(self, result: ReviewResult) -> None: if self.format == "json": self.console.print_json(json.dumps(result.to_json_dict(), indent=2)) else: - self.console.print(result.to_markdown()) \ No newline at end of file + self.console.print(result.to_markdown()) diff --git a/src/codespy/agents/reviewer/reviewer.py b/src/codespy/agents/reviewer/reviewer.py index 50f69cd..c6ba5e0 100644 --- a/src/codespy/agents/reviewer/reviewer.py +++ b/src/codespy/agents/reviewer/reviewer.py @@ -2,66 +2,43 @@ import asyncio import logging +import uuid from pathlib import Path import dspy # type: ignore[import-untyped] -from codespy.agents import SignatureContext, configure_dspy, get_cost_tracker, verify_model_access -from codespy.config import Settings, get_settings -from codespy.tools.git import GitClient, get_client, ChangedFile, MergeRequest -from codespy.tools.git.local_diff import build_mr_from_diff +from codespy.agents import configure_dspy, get_cost_tracker, verify_model_access +from codespy.agents.memory.hippocampus import ContextMemory from codespy.agents.reviewer.models import ( Issue, - SignatureStatsResult, - ReviewResult, - ReviewConfig, - RemoteReviewConfig, LocalReviewConfig, + PRContext, + RemoteReviewConfig, + ReviewConfig, + ReviewContext, + ReviewMetadata, + ReviewResult, + SignatureStatsResult, ) from codespy.agents.reviewer.modules import ( + Auditor, CodeReviewer, DocReviewer, - ScopeIdentifier, + ScopeResolver, + Summarizer, SupplyChainAuditor, ) +from codespy.agents.reviewer.modules.helpers import build_patches +from codespy.agents.reviewer.modules.scope_resolver import MANIFEST_FILES, MANIFEST_GLOBS +from codespy.config import Settings, get_settings +from codespy.config_memory import verify_memory_access +from codespy.tools.git import ChangedFile, GitClient, PullRequest, get_client +from codespy.tools.git.local_diff import build_pr_from_diff +from codespy.tools.git.patch_utils import compact_patches logger = logging.getLogger(__name__) -class MRSummarySignature(dspy.Signature): - """Generate an overall summary and recommendation for a merge request. - - You are a busy Principal Engineer. Be extremely terse. State facts only. - - Based on all the issues found during review, provide: - - A concise summary of what the MR does - - An overall assessment of the code quality - - A recommendation (approve, request changes, or needs discussion) - - OUTPUT RULES: Be direct and terse. No polite filler ("I suggest", "Great job", "Well done"). - No conversational language. State facts and assessments only. - """ - - mr_title: str = dspy.InputField(desc="Title of the merge request") - mr_description: str = dspy.InputField(desc="Description/body of the MR") - changed_files: list[ChangedFile] = dspy.InputField( - desc="In-scope reviewable files (excludes binaries, vendor, lock files, etc.) with status and line counts" - ) - all_issues: list[Issue] = dspy.InputField( - desc="All issues found during review" - ) - - summary: str = dspy.OutputField( - desc="2-3 sentence summary of what this MR accomplishes" - ) - quality_assessment: str = dspy.OutputField( - desc="Overall assessment of code quality (e.g., well-structured, needs refactoring, follows best practices, etc.)" - ) - recommendation: str = dspy.OutputField( - desc="One of: APPROVE, REQUEST_CHANGES, or NEEDS_DISCUSSION with brief justification" - ) - - class ReviewPipeline(dspy.Module): """Orchestrates the code review process using DSPy modules.""" @@ -74,10 +51,12 @@ def __init__(self, settings: Settings | None = None) -> None: configure_dspy(self.settings) # Initialize all modules - they internally check if their signatures are enabled - self.scope_identifier = ScopeIdentifier() + self.scope_resolver = ScopeResolver() self.code_reviewer = CodeReviewer() self.doc_reviewer = DocReviewer() self.supply_chain_auditor = SupplyChainAuditor() + self.summarizer = Summarizer() + self.auditor = Auditor() def _verify_model_access(self) -> None: """Verify LLM model access.""" @@ -87,34 +66,42 @@ def _verify_model_access(self) -> None: raise ValueError(f"Model access failed: {message}") logger.info(f"Model access: {message}") + def _verify_memory_access(self) -> None: + """Verify memory storage access.""" + logger.info("Verifying memory storage access...") + success, message = verify_memory_access(self.settings) + if not success: + raise ValueError(f"Memory storage access failed: {message}") + logger.info(f"Memory storage: {message}") + def _get_git_client(self, url: str) -> GitClient: """Get or create a Git client for the given URL.""" if self._git_client is None: self._git_client = get_client(url, self.settings) return self._git_client - def _fetch_mr(self, mr_url: str) -> MergeRequest: - """Fetch merge request data from Git platform.""" - client = self._get_git_client(mr_url) - logger.info(f"Fetching MR data from {client.platform_name}...") - mr = client.fetch_merge_request(mr_url) - logger.info(f"MR #{mr.number}: {mr.title} ({len(mr.changed_files)} files)") - return mr + def _fetch_pr(self, pr_url: str) -> PullRequest: + """Fetch pull request data from Git platform.""" + client = self._get_git_client(pr_url) + logger.info(f"Fetching PR data from {client.platform_name}...") + pr = client.fetch_pull_request(pr_url) + logger.info(f"PR #{pr.number}: {pr.title} ({len(pr.changed_files)} files)") + return pr - def _get_repo_path(self, mr: MergeRequest) -> Path: + def _get_repo_path(self, pr: PullRequest) -> Path: """Get the local repository path for a MR, creating directories if needed.""" cache_dir = self.settings.cache_dir cache_dir.mkdir(parents=True, exist_ok=True) # Handle nested namespaces for GitLab - owner_path = mr.repo_owner.replace("/", "_") - return cache_dir / owner_path / mr.repo_name + owner_path = pr.repo_owner.replace("/", "_") + return cache_dir / owner_path / pr.repo_name async def _run_review_modules( self, scopes: list, - repo_path: Path, module_names: list[str], - ) -> list[Issue]: + review_context: ReviewContext, + ) -> tuple[list[Issue], dict[str, ContextMemory | None]]: """Run review modules concurrently in a single event loop. Uses asyncio.gather instead of dspy.Parallel to avoid the @@ -123,38 +110,41 @@ async def _run_review_modules( Args: scopes: Identified scopes with changed files - repo_path: Path to the cloned repository module_names: Names of modules (for error logging) + review_context: ReviewContext for Hippocampus question and memory inheritance Returns: - Aggregated list of issues from all modules + Tuple of (aggregated list of issues, dict of module_name -> context_memory) """ tasks = [ - self.code_reviewer.aforward(scopes=scopes, repo_path=repo_path), - self.doc_reviewer.aforward(scopes=scopes, repo_path=repo_path), - self.supply_chain_auditor.aforward(scopes=scopes, repo_path=repo_path), + self.code_reviewer.aforward(scopes=scopes, review_context=review_context), + self.doc_reviewer.aforward(scopes=scopes, review_context=review_context), + self.supply_chain_auditor.aforward(scopes=scopes, review_context=review_context), ] results = await asyncio.gather(*tasks, return_exceptions=True) all_issues: list[Issue] = [] + context_memories: dict[str, ContextMemory | None] = {} for i, result in enumerate(results): if isinstance(result, Exception): - logger.error(f"{module_names[i]} failed: {result}") + logger.error(f"{module_names[i]} failed: {result}", exc_info=result) elif result is not None: - all_issues.extend(result) - return all_issues + issues, ctx_mem = result + all_issues.extend(issues) + context_memories[module_names[i]] = ctx_mem + return all_issues, context_memories + + def _build_local_pr(self, config: LocalReviewConfig) -> PullRequest: + """Build a PullRequest from local git changes. - def _build_local_mr(self, config: LocalReviewConfig) -> MergeRequest: - """Build a MergeRequest from local git changes. - Args: config: Local review configuration - + Returns: - MergeRequest object built from local git changes + PullRequest object built from local git changes """ - logger.info(f"Building MR from local changes in {config.repo_path}...") - return build_mr_from_diff( + logger.info(f"Building PR from local changes in {config.repo_path}...") + return build_pr_from_diff( repo_path=config.repo_path, base_ref=config.base_ref, include_uncommitted=config.uncommitted @@ -170,29 +160,44 @@ def forward(self, config: ReviewConfig) -> ReviewResult: ReviewResult with issues, summary, costs, etc. """ self.cost_tracker.reset() - + + # Generate a single run_id shared across all agents/modules invoked + # within this pipeline run, used to correlate Episode records. + run_id = uuid.uuid4().hex + # Always verify model access self._verify_model_access() - # Determine mode and fetch/build MR accordingly + # Verify memory storage access + self._verify_memory_access() + + # Determine mode and fetch/build PR accordingly if isinstance(config, RemoteReviewConfig): # Remote mode: fetch from GitHub/GitLab logger.info(f"Starting review of {config.url}") - mr = self._fetch_mr(config.url) - repo_path = self._get_repo_path(mr) + pr = self._fetch_pr(config.url) + repo_path = self._get_repo_path(pr) elif isinstance(config, LocalReviewConfig): - # Local mode: build MR from local git changes + # Local mode: build PR from local git changes mode = "uncommitted changes" if config.uncommitted else f"changes vs {config.base_ref}" logger.info(f"Starting local review: {mode} in {config.repo_path}") - mr = self._build_local_mr(config) + pr = self._build_local_pr(config) repo_path = config.repo_path.resolve() else: raise ValueError(f"Invalid config type: {type(config)}") - # Identify scopes (the module internally checks if signature is enabled) + # Step 1: Identify scopes FIRST is_local = isinstance(config, LocalReviewConfig) logger.info("Identifying code scopes...") - scopes = self.scope_identifier(mr, repo_path, is_local=is_local) + pr_context = PRContext( + repo_slug=pr.repo_slug, + pr_number=pr.number, + pr_title=pr.title, + summary=pr.title, # Use title as placeholder since summary hasn't run + ) + metadata = ReviewMetadata(repo_path=repo_path, run_id=run_id, pr=pr, is_local=is_local) + review_ctx = ReviewContext(pr_context=pr_context, memory=None, metadata=metadata) + scopes, initial_memory = self.scope_resolver(review_context=review_ctx) for scope in scopes: logger.info(f" Scope: {scope.subroot} ({scope.scope_type.value}) - {len(scope.changed_files)} files") if scope.package_manifest: @@ -201,59 +206,67 @@ def forward(self, config: ReviewConfig) -> ReviewResult: if manifest.lock_file_path: logger.info(f" Lock file: {manifest.lock_file_path}") if manifest.dependencies_changed: - logger.info(f" Dependencies changed: Yes") - - # Run review modules concurrently via asyncio.gather + logger.info(" Dependencies changed: Yes") + # Expand sparse checkout to cover full scope subtrees + if not is_local: + self._expand_sparse_for_scopes(scopes, repo_path) + # Compact patches: expand context to function bodies for better review context + logger.info("Compacting patches to function boundaries...") + changed_file_paths = [f.filename for f in pr.changed_files] + patches = build_patches(pr.changed_files) + compact_patches(scopes, repo_path) + # Step 2: Run Summarizer (now receives scopes for per-scope episode persistence) + # Compute all scope topic IDs for summarizer + all_scope_topic_ids = [s.topic(pr.repo_full_name).id for s in scopes] + pr_summary, summarizer_memory = self.summarizer( + pr_title=pr.title, + pr_description=pr.body or "No description provided.", + pr_number=pr.number, + changed_file_paths=changed_file_paths, + patches=patches, + repo_slug=pr.repo_slug, + run_id=run_id, + scopes=scopes, + initial_memory=initial_memory, + topic_ids=all_scope_topic_ids, + ) + # Enrich review_ctx with actual summary and memory from summarizer + pr_context.summary = pr_summary + review_ctx = ReviewContext(pr_context=pr_context, memory=summarizer_memory, metadata=metadata) + # Step 3: Run review modules concurrently via asyncio.gather (inherit Scope Identifier memory) module_names = ["code_reviewer", "doc_reviewer", "supply_chain_auditor"] logger.info(f"Running review modules concurrently: {', '.join(module_names)}...") - all_issues = asyncio.run( - self._run_review_modules(scopes, repo_path, module_names) + all_issues, parallel_memories = asyncio.run( + self._run_review_modules(scopes, module_names, review_context=review_ctx) ) logger.info(f"Found {len(all_issues)} issues") - - # Collect in-scope files from identified scopes (excludes binaries, vendor, lock files, etc.) + # Merge parallel context memories for Auditor + memories_to_merge = [m for m in parallel_memories.values() if m is not None] + merged_memory = ContextMemory.merge(*memories_to_merge) if memories_to_merge else summarizer_memory + review_ctx = ReviewContext(pr_context=pr_context, memory=merged_memory, metadata=metadata) + # Step 4: Run Audit (inherits merged memory from parallel modules) scoped_files = self._collect_scoped_files(scopes) logger.info( - f"Summary input: {len(scoped_files)} in-scope files " - f"(filtered from {len(mr.changed_files)} total)" + f"Audit input: {len(scoped_files)} in-scope files " + f"(filtered from {len(pr.changed_files)} total)" + ) + quality_assessment, recommendation = self.auditor( + review_context=review_ctx, + changed_files=scoped_files, + all_issues=all_issues, + run_id=run_id, ) - # Generate summary, quality assessment, and recommendation - if self.settings.is_signature_enabled("summarization"): - logger.info("Generating MR summary...") - try: - summarizer = dspy.ChainOfThought(MRSummarySignature) - # Track the summarization signature's costs - with SignatureContext("summarization", self.cost_tracker): - result = summarizer( - mr_title=mr.title, - mr_description=mr.body or "No description provided.", - changed_files=scoped_files, - all_issues=all_issues, - ) - summary = result.summary - quality_assessment = result.quality_assessment - recommendation = result.recommendation - except Exception as e: - logger.error(f"Failed to generate summary: {e}") - summary = f"Reviewed {len(scoped_files)} files with {len(all_issues)} issues." - quality_assessment = "Unable to assess due to error." - recommendation = "NEEDS_DISCUSSION: Summary generation failed." - else: - logger.debug("Skipping summarization: disabled") - summary = f"Reviewed {len(scoped_files)} files with {len(all_issues)} issues." - quality_assessment = "Summarization disabled." - recommendation = "NEEDS_DISCUSSION" if all_issues else "APPROVE" # Collect per-signature statistics signature_stats_list = self._collect_signature_stats() - return ReviewResult( - mr_number=mr.number, - mr_title=mr.title, - mr_url=mr.url, - repo=mr.repo_full_name, + pr_number=pr.number, + pr_title=pr.title, + pr_url=pr.url, + repo=pr.repo_full_name, + run_id=run_id, model_used=self.settings.default_model, issues=all_issues, - overall_summary=summary, + overall_summary=pr_summary, quality_assessment=quality_assessment, recommendation=recommendation, total_cost=self.cost_tracker.total_cost, @@ -271,7 +284,7 @@ def _collect_scoped_files(scopes: list) -> list[ChangedFile]: summarizer operates on the same focused set as the review modules. Args: - scopes: Identified scopes from scope_identifier + scopes: Identified scopes from scope_resolver Returns: De-duplicated list of ChangedFile objects from all scopes @@ -303,4 +316,43 @@ def _collect_signature_stats(self) -> list[SignatureStatsResult]: duration_seconds=stats.duration_seconds, )) - return stats_list \ No newline at end of file + return stats_list + + def _expand_sparse_for_scopes( + self, scopes: list, repo_path: Path + ) -> None: + """Expand sparse checkout to cover full subtree of each identified scope. + + Called after scope identification, before compact_patches and review modules, + to ensure read_file and patch compaction have full scope context available. + """ + from git import Repo + + git_dir = repo_path / ".git" + if not git_dir.exists(): + return + + # Build scope-aware sparse paths + sparse_paths: set[str] = set() + for scope in scopes: + if scope.subroot == ".": + # Root scope — need everything; disable sparse checkout effectively + sparse_paths.add("/*") + sparse_paths.add("*/") + break + else: + sparse_paths.add(scope.subroot.rstrip("/") + "/") + + # Always include root-level files and manifests + sparse_paths.add("/*") + for manifest in MANIFEST_FILES: + sparse_paths.add(manifest) + for pattern in MANIFEST_GLOBS: + sparse_paths.add(pattern) + + sparse_file = git_dir / "info" / "sparse-checkout" + sparse_file.write_text("\n".join(sorted(sparse_paths)) + "\n") + + # Re-checkout to materialize newly included paths + repo = Repo(repo_path) + repo.git.checkout() diff --git a/src/codespy/agents/reviewer/server.py b/src/codespy/agents/reviewer/server.py index b77f60d..819ee6f 100644 --- a/src/codespy/agents/reviewer/server.py +++ b/src/codespy/agents/reviewer/server.py @@ -77,13 +77,13 @@ def _do_uncommitted_review(repo_path: str, output_format: str) -> str: return result.to_markdown() -def _do_pr_review(mr_url: str, output_format: str) -> str: +def _do_pr_review(pr_url: str, output_format: str) -> str: """Synchronous PR review — runs in thread pool.""" import json from codespy.agents.reviewer.models import RemoteReviewConfig - config = RemoteReviewConfig(url=mr_url) + config = RemoteReviewConfig(url=pr_url) pipeline = _get_pipeline() result = pipeline(config) @@ -104,7 +104,7 @@ async def review_local_changes( No PR or remote platform required — works with any local git repository. Diffs the current HEAD against the base_ref to find changed files, then runs the full codespy review pipeline (scope identification, code & doc review, - supply chain audit, and summarization). + supply chain audit, summary, and audit). Args: repo_path: Absolute path to the local git repository to review @@ -154,7 +154,7 @@ async def review_uncommitted( @mcp.tool() async def review_pr( - mr_url: str, + pr_url: str, output_format: str = "markdown", ) -> str: """Review a GitHub Pull Request or GitLab Merge Request by URL. @@ -163,7 +163,7 @@ async def review_pr( the full codespy review pipeline. Args: - mr_url: Full URL of the PR/MR to review. + pr_url: Full URL of the PR/MR to review. GitHub: https://github.com/owner/repo/pull/123 GitLab: https://gitlab.com/namespace/project/-/merge_requests/123 output_format: Output format — "markdown" for human-readable or "json" for structured data. @@ -173,7 +173,7 @@ async def review_pr( Review results as markdown or JSON string """ try: - return await _run_in_thread(_do_pr_review, mr_url, output_format) + return await _run_in_thread(_do_pr_review, pr_url, output_format) except Exception as e: logger.exception("Review failed") return f"Review failed: {e}" diff --git a/src/codespy/cli.py b/src/codespy/cli.py index 7c3677c..e4f09bb 100644 --- a/src/codespy/cli.py +++ b/src/codespy/cli.py @@ -7,13 +7,13 @@ from rich.panel import Panel from codespy import __version__ -from codespy.config import get_settings -from codespy.config_git import get_github_token_source, get_gitlab_token_source +from codespy.cli_local import review_local, review_uncommitted +from codespy.cli_mcp_server import serve # Import command functions from submodules from codespy.cli_remote import review -from codespy.cli_local import review_local, review_uncommitted -from codespy.cli_mcp_server import serve +from codespy.config import get_settings +from codespy.config_git import get_github_token_source, get_gitlab_token_source app = typer.Typer( name="codespy", @@ -77,7 +77,8 @@ def config( # Show non-sensitive settings console.print(f"[bold]Model:[/bold] {settings.default_model}") console.print(f"[bold]AWS Region:[/bold] {settings.aws_region}") - console.print(f"[bold]Max Context Size:[/bold] {settings.default_max_context_size}") + console.print(f"[bold]Reasoning Effort:[/bold] {settings.default_reasoning_effort}") + console.print(f"[bold]Output Format:[/bold] {settings.output_format}") console.print(f"[bold]Cache Directory:[/bold] {settings.cache_dir}") diff --git a/src/codespy/cli_local.py b/src/codespy/cli_local.py index 649f710..e479ced 100644 --- a/src/codespy/cli_local.py +++ b/src/codespy/cli_local.py @@ -86,11 +86,11 @@ def review_local( settings.output_format = output # type: ignore repo = Path(repo_path if repo_path else os.getcwd()).resolve() - + if not repo.exists(): console.print(f"[red]Error:[/red] Directory does not exist: {repo}") raise typer.Exit(1) - + if not (repo / ".git").exists(): console.print(f"[red]Error:[/red] Not a git repository: {repo}") raise typer.Exit(1) @@ -107,8 +107,8 @@ def review_local( ) try: - from codespy.agents.reviewer.reviewer import ReviewPipeline from codespy.agents.reviewer.models import LocalReviewConfig + from codespy.agents.reviewer.reviewer import ReviewPipeline pipeline = ReviewPipeline(settings) @@ -118,7 +118,7 @@ def review_local( base_ref=base_ref, uncommitted=False ) - + # Run review (model access always verified in pipeline) result = pipeline(config) @@ -203,11 +203,11 @@ def review_uncommitted( settings.output_format = output # type: ignore repo = Path(repo_path if repo_path else os.getcwd()).resolve() - + if not repo.exists(): console.print(f"[red]Error:[/red] Directory does not exist: {repo}") raise typer.Exit(1) - + if not (repo / ".git").exists(): console.print(f"[red]Error:[/red] Not a git repository: {repo}") raise typer.Exit(1) @@ -223,8 +223,8 @@ def review_uncommitted( ) try: - from codespy.agents.reviewer.reviewer import ReviewPipeline from codespy.agents.reviewer.models import LocalReviewConfig + from codespy.agents.reviewer.reviewer import ReviewPipeline pipeline = ReviewPipeline(settings) @@ -233,7 +233,7 @@ def review_uncommitted( repo_path=repo, uncommitted=True ) - + # Run review (model access always verified in pipeline) result = pipeline(config) diff --git a/src/codespy/cli_remote.py b/src/codespy/cli_remote.py index e93823e..7610e2e 100644 --- a/src/codespy/cli_remote.py +++ b/src/codespy/cli_remote.py @@ -16,10 +16,10 @@ def review( - mr_url: Annotated[ + pr_url: Annotated[ str, typer.Argument( - help="Merge request URL (GitHub PR or GitLab MR)", + help="Pull request URL (GitHub PR or GitLab MR)", ), ], config_file: Annotated[ @@ -87,7 +87,7 @@ def review( # Print config at startup (secrets are hidden via repr=False) logging.info(f"Loaded config: {settings}") - + # Log module configurations settings.log_signature_configs() @@ -102,7 +102,7 @@ def review( settings.output_git = git_comment # Validate URL format - if not is_supported_url(mr_url): + if not is_supported_url(pr_url): console.print( "[red]Error:[/red] Unsupported URL format.\n\n" "Supported formats:\n" @@ -112,8 +112,8 @@ def review( raise typer.Exit(1) # Detect platform and validate token - platform = detect_platform(mr_url) - + platform = detect_platform(pr_url) + if platform == "github": token = settings.github_token token_source = get_github_token_source() @@ -146,7 +146,7 @@ def review( console.print( Panel( - f"[bold blue]Reviewing MR:[/bold blue] {mr_url}\n" + f"[bold blue]Reviewing PR:[/bold blue] {pr_url}\n" f"[bold]Platform:[/bold] {platform.title()}\n" f"[bold]Model:[/bold] {settings.default_model}\n" f"[bold]Output:[/bold] {output_display}\n" @@ -156,14 +156,14 @@ def review( ) try: - from codespy.agents.reviewer.reviewer import ReviewPipeline from codespy.agents.reviewer.models import RemoteReviewConfig + from codespy.agents.reviewer.reviewer import ReviewPipeline pipeline = ReviewPipeline(settings) # Create remote review config - config = RemoteReviewConfig(url=mr_url) - + config = RemoteReviewConfig(url=pr_url) + # Run review (model access always verified in pipeline) result = pipeline(config) @@ -188,7 +188,7 @@ def review( if settings.output_git: console.print(f"[dim]Posting review to {platform.title()}...[/dim]") - git_reporter = GitReporter(url=mr_url, settings=settings) + git_reporter = GitReporter(url=pr_url, settings=settings) git_reporter.report(result) console.print(f"[green]✓[/green] {platform.title()} review posted successfully") diff --git a/src/codespy/config.py b/src/codespy/config.py index 2756abd..5b9815f 100644 --- a/src/codespy/config.py +++ b/src/codespy/config.py @@ -2,13 +2,17 @@ import logging from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any import yaml from pydantic import Field, model_validator from pydantic_settings import BaseSettings, SettingsConfigDict -from codespy.config_dspy import SignatureConfig, apply_signature_env_overrides +from codespy.config_dspy import ( + ReasoningEffort, + SignatureConfig, + apply_signature_env_overrides, +) from codespy.config_git import ( GitHubConfig, GitLabConfig, @@ -27,6 +31,23 @@ discover_gemini_api_key, discover_openai_api_key, ) +from codespy.config_memory import ( + REFLECTION_MODULES, + LLMSettings, + MemoryConfig, + apply_memory_env_overrides, + reset_memory_store, +) + +if TYPE_CHECKING: + # Imported lazily inside get_memory_budget(): importing this at module level + # pulls in codespy.agents, whose __init__ imports dspy_config, which imports + # this module — a circular import that breaks every entrypoint. + from codespy.agents.memory.hippocampus.budget import MemoryBudget + + + + logger = logging.getLogger(__name__) @@ -44,6 +65,7 @@ "GitHubConfig", "GitLabConfig", "SignatureConfig", + "MemoryConfig", "OutputFormat", ] @@ -92,17 +114,26 @@ class Settings(BaseSettings): llm: LLMConfig = Field(default_factory=LLMConfig) github: GitHubConfig = Field(default_factory=GitHubConfig) gitlab: GitLabConfig = Field(default_factory=GitLabConfig) + memory: MemoryConfig = Field(default_factory=MemoryConfig) # Flat signature configs (signature_name -> SignatureConfig) signatures: dict[str, SignatureConfig] = Field(default_factory=dict) # Top-level defaults (also available via env vars DEFAULT_MODEL, etc.) default_model: str = "anthropic/claude-opus-4-6" - extraction_model: str | None = None # For TwoStepAdapter field extraction (falls back to default_model) - default_max_iters: int = 3 - default_max_context_size: int = 50000 - default_max_reasoning_tokens: int = 8000 # Limit reasoning verbosity for adapter reliability - default_temperature: float = 0.1 # Lower = more deterministic JSON output + extraction_model: str | None = None # TwoStepAdapter extraction (falls back to default_model) + default_max_iters: int = 10 + # Provider reasoning budget; LiteLLM maps this to each provider's native parameter. + default_reasoning_effort: ReasoningEffort = "medium" + default_temperature: float = 0.2 + # Output token budget per completion. Must be set explicitly: when it is + # omitted LiteLLM silently falls back to its own 4096 default, which + # truncates reasoning models (thinking tokens are charged against this + # budget) and long structured outputs. 64000 matches the output ceiling of + # the Claude 4.x tier and satisfies dspy.LM's >=16000 guard for OpenAI + # reasoning models; new_lm() clamps it down to each model's real ceiling. + default_max_tokens: int = 64000 + # Global LLM reliability settings llm_retries: int = 3 # Number of retries for LLM API calls @@ -156,30 +187,48 @@ def is_signature_enabled(self, signature_name: str) -> bool: """Check if a signature is enabled.""" return self.get_signature_config(signature_name).enabled - def get_model(self, signature_name: str) -> str: - """Get model for a signature (signature-specific or default).""" - config = self.get_signature_config(signature_name) - return config.model or self.default_model - def get_max_iters(self, signature_name: str) -> int: """Get max_iters for a signature (signature-specific or default).""" config = self.get_signature_config(signature_name) return config.max_iters or self.default_max_iters - def get_max_context_size(self, signature_name: str) -> int: - """Get max_context_size for a signature (signature-specific or default).""" - config = self.get_signature_config(signature_name) - return config.max_context_size or self.default_max_context_size + def get_llm_config(self, name: str) -> LLMSettings: + """Resolve the LLM settings for one named unit of LLM work. - def get_max_reasoning_tokens(self, signature_name: str) -> int: - """Get max_reasoning_tokens for a signature (signature-specific or default).""" - config = self.get_signature_config(signature_name) - return config.max_reasoning_tokens or self.default_max_reasoning_tokens + ``name`` addresses either a signature or a memory reflection module:: + + "code_review" -> signatures.code_review + "distiller" -> memory.distiller + + Every field falls back to its top-level ``default_*`` counterpart, so + the result has no ``None`` fields and callers never re-apply fallbacks. + + Args: + name: A signature name, or a reflection module name + (see ``REFLECTION_MODULES``). + + Returns: + The fully resolved settings for ``name``. + """ + if name in REFLECTION_MODULES: + config = getattr(self.memory, name) + else: + config = self.get_signature_config(name) + + model = config.model or self.default_model + module_extraction = getattr(config, "extraction_model", None) + return LLMSettings( + model=model, + extraction_model=module_extraction or self.extraction_model or model, + reasoning_effort=config.reasoning_effort or self.default_reasoning_effort, + temperature=( + config.temperature + if config.temperature is not None + else self.default_temperature + ), + max_tokens=config.max_tokens or self.default_max_tokens, + ) - def get_temperature(self, signature_name: str) -> float: - """Get temperature for a signature (signature-specific or default).""" - config = self.get_signature_config(signature_name) - return config.temperature if config.temperature is not None else self.default_temperature def get_scan_unchanged(self, signature_name: str) -> bool: """Get scan_unchanged for a signature (signature-specific, default: False). @@ -190,20 +239,61 @@ def get_scan_unchanged(self, signature_name: str) -> bool: config = self.get_signature_config(signature_name) return config.scan_unchanged if config.scan_unchanged is not None else False + # Helper methods for per-signature memory config (Hippocampus) + def get_memory_enabled(self, signature_name: str) -> bool: + """Whether Hippocampus memory is enabled for a signature. + + Per-signature ``memory.enabled`` overrides ``memory.default_enabled``. + """ + config = self.get_signature_config(signature_name).memory + return config.enabled if config.enabled is not None else self.memory.default_enabled + + def get_memory_max_reflects(self, signature_name: str) -> int | None: + """Get max_reflects for a signature's memory (signature-specific or default).""" + config = self.get_signature_config(signature_name).memory + return ( + config.max_reflects + if config.max_reflects is not None + else self.memory.default_max_reflects + ) + + def get_memory_budget(self, signature_name: str) -> "MemoryBudget": + """Resolve the ``MemoryBudget`` for a signature. + + Token budgets are global (``memory.default_*``); only ``enabled`` and + ``max_reflects`` support per-signature overrides. + """ + from codespy.agents.memory.hippocampus.budget import MemoryBudget + + return MemoryBudget( + max_context_memory_tokens=self.memory.default_max_context_memory_tokens, + max_context_item_tokens=self.memory.default_max_context_item_tokens, + max_trajectory_tokens=self.memory.default_max_trajectory_tokens, + max_question_tokens=self.memory.default_max_question_tokens, + ) + def log_signature_configs(self) -> None: - """Log all signature configurations.""" + """Log all signature and reflection module LLM configurations.""" logger.info("Signature configurations:") for sig_name, sig_config in self.signatures.items(): status = "enabled" if sig_config.enabled else "disabled" - model = sig_config.model or self.default_model - max_iters = sig_config.max_iters or self.default_max_iters - max_reasoning = sig_config.max_reasoning_tokens or self.default_max_reasoning_tokens - temp = sig_config.temperature if sig_config.temperature is not None else self.default_temperature + llm = self.get_llm_config(sig_name) + logger.info( + f" {sig_name}: {status}, model={llm.model}, " + f"max_iters={self.get_max_iters(sig_name)}, " + f"reasoning_effort={llm.reasoning_effort}, temperature={llm.temperature}, " + f"max_tokens={llm.max_tokens}" + ) + for module in REFLECTION_MODULES: + llm = self.get_llm_config(module) logger.info( - f" {sig_name}: {status}, model={model}, max_iters={max_iters}, " - f"max_reasoning_tokens={max_reasoning}, temperature={temp}" + f" {module}: model={llm.model}, " + f"extraction_model={llm.extraction_model}, " + f"reasoning_effort={llm.reasoning_effort}, temperature={llm.temperature}, " + f"max_tokens={llm.max_tokens}" ) + @model_validator(mode="before") @classmethod def load_yaml_config(cls, values: dict[str, Any]) -> dict[str, Any]: @@ -213,6 +303,10 @@ def load_yaml_config(cls, values: dict[str, Any]) -> dict[str, Any]: """ yaml_config = _load_yaml_config() yaml_config = apply_signature_env_overrides(yaml_config) + # MEMORY_* env vars target the nested `memory` model, which + # pydantic-settings cannot populate on its own (no env_nested_delimiter). + yaml_config = apply_memory_env_overrides(yaml_config) + # Merge YAML config into values only if not already set (env vars take precedence) for key, val in yaml_config.items(): @@ -450,6 +544,9 @@ def get_settings(config_file: str | None = None) -> Settings: def reload_settings(config_file: str | None = None) -> Settings: """Reload settings (useful after environment changes). + Also resets the cached Hippocampus memory store so a changed + ``memory`` configuration takes effect on next access. + Args: config_file: Optional path to a YAML config file. If provided, uses that file instead of the default locations. @@ -458,4 +555,5 @@ def reload_settings(config_file: str | None = None) -> Settings: if config_file is not None: _custom_config_path = config_file settings = Settings() + reset_memory_store() return settings diff --git a/src/codespy/config_dspy.py b/src/codespy/config_dspy.py index 32d847b..d2ca9a8 100644 --- a/src/codespy/config_dspy.py +++ b/src/codespy/config_dspy.py @@ -2,24 +2,44 @@ import logging import os -from typing import Any +from typing import Any, Literal -from pydantic import BaseModel +from pydantic import BaseModel, Field logger = logging.getLogger(__name__) +# Reasoning budget hint sent to the provider. LiteLLM normalises this to each +# provider's native parameter (Anthropic thinking budget, OpenAI reasoning +# effort, ...), so it works across every supported model. +ReasoningEffort = Literal["minimal", "low", "medium", "high"] + + +class MemorySignatureConfig(BaseModel): + + """Per-signature Hippocampus memory overrides. + + All fields are optional — ``None`` means "use the global memory default" + (see ``codespy.config_memory.MemoryConfig``). + """ + + enabled: bool | None = None # _MEMORY_ENABLED + max_reflects: int | None = None # _MEMORY_MAX_REFLECTS + + class SignatureConfig(BaseModel): """Configuration for a single signature.""" enabled: bool = True max_iters: int | None = None model: str | None = None - max_context_size: int | None = None - max_reasoning_tokens: int | None = None # Limit reasoning verbosity for JSONAdapter reliability - temperature: float | None = None # Lower = more deterministic JSON output + reasoning_effort: ReasoningEffort | None = None # Provider reasoning budget + temperature: float | None = None + max_tokens: int | None = None # Output token budget (reasoning tokens included) scan_unchanged: bool | None = None # For supply_chain: scan unmodified artifacts/manifests + memory: MemorySignatureConfig = Field(default_factory=MemorySignatureConfig) + # Known signature names for env var routing SIGNATURE_NAMES = { @@ -27,14 +47,21 @@ class SignatureConfig(BaseModel): "doc", "scope", "supply_chain", - "summarization", + "summary", + "audit", } # Create uppercase prefixes for matching (e.g., "CODE_REVIEW_", "SUPPLY_CHAIN_") SIGNATURE_PREFIXES = {name.upper() + "_": name for name in SIGNATURE_NAMES} -# Known signature settings for validation -SIGNATURE_SETTINGS = {"enabled", "max_iters", "model", "max_context_size", "max_reasoning_tokens", "temperature", "scan_unchanged"} +# Known signature settings for validation, derived from the models so the env +# var routing can never drift from the declared fields. ``memory`` is excluded +# because it is nested and routed via _MEMORY_ instead. +SIGNATURE_SETTINGS = set(SignatureConfig.model_fields) - {"memory"} + +# Known per-signature memory settings, routed via _MEMORY_ +MEMORY_SIGNATURE_SETTINGS = set(MemorySignatureConfig.model_fields) + def convert_env_value(value: str) -> Any: @@ -58,12 +85,13 @@ def convert_env_value(value: str) -> Any: def apply_signature_env_overrides(config: dict[str, Any]) -> dict[str, Any]: """Apply environment variable overrides to config for signature settings. - Handles signature settings with pattern: - - CODE_REVIEW_MAX_ITERS -> signatures.code_review.max_iters - - SUPPLY_CHAIN_ENABLED -> signatures.supply_chain.enabled + Handles three patterns: + - ``CODE_REVIEW_MAX_ITERS`` -> signatures.code_review.max_iters + - ``SUPPLY_CHAIN_ENABLED`` -> signatures.supply_chain.enabled + - ``CODE_REVIEW_MEMORY_ENABLED`` -> signatures.code_review.memory.enabled - Top-level settings (DEFAULT_MODEL, AWS_REGION, etc.) are handled directly - by pydantic-settings and should NOT be processed here. + Top-level settings (DEFAULT_MODEL, AWS_REGION, MEMORY_DEFAULT_ENABLED, etc.) + are handled directly by pydantic-settings and should NOT be processed here. """ # Load .env file first to ensure env vars are available from dotenv import dotenv_values @@ -75,27 +103,41 @@ def apply_signature_env_overrides(config: dict[str, Any]) -> dict[str, Any]: continue key_upper = key.upper() - # Only process signature-specific settings + # Match signature prefix signature_name = None - setting = None - + remainder = None for prefix, sig_name in SIGNATURE_PREFIXES.items(): if key_upper.startswith(prefix): signature_name = sig_name - setting = key_upper[len(prefix) :].lower() + remainder = key_upper[len(prefix):] break - # Skip if not a signature setting or not a valid setting name - if not signature_name or setting not in SIGNATURE_SETTINGS: + if not signature_name or remainder is None: continue - # Ensure signatures dict exists + # Nested memory setting: _MEMORY_ + if remainder.startswith("MEMORY_"): + memory_setting = remainder[len("MEMORY_"):].lower() + if memory_setting not in MEMORY_SIGNATURE_SETTINGS: + continue + if "signatures" not in config: + config["signatures"] = {} + if signature_name not in config["signatures"]: + config["signatures"][signature_name] = {} + if "memory" not in config["signatures"][signature_name]: + config["signatures"][signature_name]["memory"] = {} + sig_memory = config["signatures"][signature_name]["memory"] + sig_memory[memory_setting] = convert_env_value(value) + continue + + # Flat signature setting + setting = remainder.lower() + if setting not in SIGNATURE_SETTINGS: + continue if "signatures" not in config: config["signatures"] = {} if signature_name not in config["signatures"]: config["signatures"][signature_name] = {} - - # Set the value config["signatures"][signature_name][setting] = convert_env_value(value) - return config \ No newline at end of file + return config diff --git a/src/codespy/config_git.py b/src/codespy/config_git.py index 6a9b556..6aa7563 100644 --- a/src/codespy/config_git.py +++ b/src/codespy/config_git.py @@ -195,4 +195,4 @@ class GitLabConfig(BaseModel): token: str | None = Field(default=None, repr=False) url: str = "https://gitlab.com" # Can be changed for self-hosted instances - auto_discover_token: bool = True \ No newline at end of file + auto_discover_token: bool = True diff --git a/src/codespy/config_io.py b/src/codespy/config_io.py index 03606e2..d3890c5 100644 --- a/src/codespy/config_io.py +++ b/src/codespy/config_io.py @@ -37,4 +37,4 @@ ".pytest_cache", ".mypy_cache", ".ruff_cache", -] \ No newline at end of file +] diff --git a/src/codespy/config_memory.py b/src/codespy/config_memory.py new file mode 100644 index 0000000..8cd2d46 --- /dev/null +++ b/src/codespy/config_memory.py @@ -0,0 +1,337 @@ +"""Memory (Hippocampus) configuration and storage factory.""" + +from __future__ import annotations + +import os +from typing import TYPE_CHECKING, Any, Literal + +from pydantic import BaseModel, Field + +from codespy.config_dspy import ReasoningEffort +from codespy.tools.storage.base import Storage + +if TYPE_CHECKING: + from codespy.config import Settings + + +MemoryBackend = Literal["filesystem", "s3"] + + + +class ReflectionModuleConfig(BaseModel): + """LLM overrides for a single reflection module (Distiller / Cartographer). + + All fields are optional — ``None`` means "fall back to the corresponding + top-level ``default_*`` setting" (see ``codespy.config.Settings``). + + The reflection modules are compact summarize/curate tasks rather than deep + analysis, so they are good candidates for a cheaper model tier than the + one used for code review. + """ + + model: str | None = None # MEMORY__MODEL + reasoning_effort: ReasoningEffort | None = None # MEMORY__REASONING_EFFORT + temperature: float | None = None # MEMORY__TEMPERATURE + max_tokens: int | None = None # MEMORY__MAX_TOKENS + + +class LLMSettings(BaseModel): + """Fully resolved LLM settings for one named unit of work. + + Produced by ``Settings.get_llm_config()`` for either a signature + (``signatures.``) or a reflection module (``memory.``): every + field is either the name-specific override or the corresponding top-level + default, so consumers never re-apply fallback logic. + """ + + model: str + extraction_model: str + reasoning_effort: ReasoningEffort + temperature: float + # Output token budget for a single completion. Reasoning/thinking tokens are + # charged against it, so it must comfortably exceed the expected answer size. + # ``new_lm`` clamps this to the model's real output ceiling before use. + max_tokens: int + + + +class MemoryConfig(BaseModel): + + """Global memory (Hippocampus) configuration. + + Controls where episodes are persisted and the default reflection knobs + applied to every agent. Per-signature ``memory:`` blocks override the + ``default_*`` values. + """ + + # Storage backend + + backend: MemoryBackend = "filesystem" # MEMORY_BACKEND + root: str = "~/.cache/codespy/memory" # MEMORY_ROOT (filesystem backend) + s3_bucket: str | None = None # MEMORY_S3_BUCKET (s3 backend) + s3_region: str | None = None # MEMORY_S3_REGION (falls back to aws_region) + s3_endpoint_url: str | None = None # MEMORY_S3_ENDPOINT_URL (MinIO/S3-compatible) + + # Reflection defaults — overridable per-signature + default_enabled: bool = False # MEMORY_DEFAULT_ENABLED + default_max_reflects: int = Field(default=0) # MEMORY_DEFAULT_MAX_REFLECTS + + # Ceiling on the rendered ContextMemory. This is the *persisted* artifact and it + # is prepended to every predictor of the wrapped agent, so it is re-sent on + # every ReAct iteration (~default_max_iters times per scope) plus once per + # reflection call. Easily the most cost-sensitive of the three budgets. + # Approximate item capacity is default_max_context_memory_tokens divided by + # default_max_context_item_tokens (8192 / 410 ~= 19 items). + # MEMORY_DEFAULT_MAX_CONTEXT_MEMORY_TOKENS + default_max_context_memory_tokens: int = Field(default=8192) + + # Per-item ceiling handed to the Distiller/Cartographer as a prompt input, so + # they keep each context-memory item compact instead of spending the whole memory + # budget on one verbose entry. Soft limit: it is expressed to the LLM rather + # than enforced in code (truncating an item could corrupt an exact constant). + # The hard, memory-wide limit is default_max_context_memory_tokens, enforced by the + # Evictor. MEMORY_DEFAULT_MAX_CONTEXT_ITEM_TOKENS + default_max_context_item_tokens: int = Field(default=410) + + + # Head+tail cap on the agent trajectory fed to the Distiller. Without it a + # single tool-heavy scope can produce a 100k+ token trajectory; TwoStepAdapter + # then sends it twice. 8192 is ~5% of a 128k window and preserves both the + # orientation steps (60% head) and the conclusions (40% tail). + default_max_trajectory_tokens: int | None = 8192 # MEMORY_DEFAULT_MAX_TRAJECTORY_TOKENS + + # Head+tail cap on the serialized agent inputs used as the Distiller/Cartographer + # "question". Only applies when the caller passes no 'question': otherwise + # every input field is serialized, which for code review means the full patch + # of every changed file. See Hippocampus.max_question_tokens. + default_max_question_tokens: int | None = 2048 # MEMORY_DEFAULT_MAX_QUESTION_TOKENS + + + # Per-module LLM overrides for the reflection pipeline. + # Unset fields fall back to the top-level ``default_*`` settings. + distiller: ReflectionModuleConfig = Field( + default_factory=ReflectionModuleConfig + ) # MEMORY_DISTILLER_* + cartographer: ReflectionModuleConfig = Field( + default_factory=ReflectionModuleConfig + ) # MEMORY_CARTOGRAPHER_* + + + +# Env var name (without the MEMORY_ prefix) -> MemoryConfig field name. +# ``memory`` is a nested model and ``Settings`` does not set +# ``env_nested_delimiter``, so pydantic-settings cannot populate these fields +# from the environment on its own. apply_memory_env_overrides() bridges the gap. +MEMORY_ENV_SETTINGS = { + "BACKEND": "backend", + "ROOT": "root", + "S3_BUCKET": "s3_bucket", + "S3_REGION": "s3_region", + "S3_ENDPOINT_URL": "s3_endpoint_url", + "DEFAULT_ENABLED": "default_enabled", + "DEFAULT_MAX_REFLECTS": "default_max_reflects", + "DEFAULT_MAX_CONTEXT_MEMORY_TOKENS": "default_max_context_memory_tokens", + "DEFAULT_MAX_CONTEXT_ITEM_TOKENS": "default_max_context_item_tokens", + "DEFAULT_MAX_TRAJECTORY_TOKENS": "default_max_trajectory_tokens", + "DEFAULT_MAX_QUESTION_TOKENS": "default_max_question_tokens", +} + +# The reflection modules, derived from the MemoryConfig fields that hold a +# ReflectionModuleConfig. Iterate this instead of hardcoding module names so +# adding a new reflection module only requires declaring its field above. +REFLECTION_MODULES: tuple[str, ...] = tuple( + name + for name, field in MemoryConfig.model_fields.items() + if field.annotation is ReflectionModuleConfig +) + +# Env var suffix -> ReflectionModuleConfig field name, routed via +# MEMORY__ (e.g. MEMORY_DISTILLER_MODEL). +REFLECTION_MODULE_ENV_SETTINGS = { + name.upper(): name for name in ReflectionModuleConfig.model_fields +} + +# Env var prefix (after MEMORY_) -> MemoryConfig field holding the nested model. +REFLECTION_MODULE_PREFIXES = { + f"{name.upper()}_": name for name in REFLECTION_MODULES +} + + + + +def apply_memory_env_overrides(config: dict[str, Any]) -> dict[str, Any]: + """Apply ``MEMORY_*`` environment variable overrides to the ``memory`` block. + + Maps flat env vars onto the nested ``memory`` config, e.g.:: + + MEMORY_BACKEND=s3 -> memory.backend + MEMORY_DEFAULT_ENABLED=true -> memory.default_enabled + MEMORY_DEFAULT_MAX_CONTEXT_MEMORY_TOKENS=512 -> memory.default_max_context_memory_tokens + + Reflection module overrides use a second level of nesting:: + + MEMORY_DISTILLER_MODEL=... -> memory.distiller.model + MEMORY_CARTOGRAPHER_TEMPERATURE=0 -> memory.cartographer.temperature + + Env vars take precedence over YAML, matching the documented priority + (Environment Variables > YAML Config > Defaults). + + + Note: ``_MEMORY_*`` vars are handled separately by + ``apply_signature_env_overrides`` and are ignored here, since they never + match a bare ``MEMORY_`` prefix. + + Args: + config: The YAML-derived config dict to mutate. + + Returns: + The same dict, with ``memory`` overrides applied. + """ + from dotenv import dotenv_values + + from codespy.config_dspy import convert_env_value + + env_vars = {**dotenv_values(".env"), **os.environ} + + for key, value in env_vars.items(): + if value is None: + continue + key_upper = key.upper() + if not key_upper.startswith("MEMORY_"): + continue + remainder = key_upper[len("MEMORY_"):] + + memory_config = config.setdefault("memory", {}) + if not isinstance(memory_config, dict): + continue + + # Reflection module settings: MEMORY__. Checked before + # the flat lookup, since e.g. MEMORY_DISTILLER_MODEL has no entry in + # MEMORY_ENV_SETTINGS and would otherwise be silently dropped. + module_field = next( + ( + (field, remainder[len(prefix):]) + for prefix, field in REFLECTION_MODULE_PREFIXES.items() + if remainder.startswith(prefix) + ), + None, + ) + if module_field is not None: + field, setting = module_field + module_setting = REFLECTION_MODULE_ENV_SETTINGS.get(setting) + if module_setting is None: + continue + module_config = memory_config.setdefault(field, {}) + if not isinstance(module_config, dict): + continue + module_config[module_setting] = convert_env_value(value) + continue + + field = MEMORY_ENV_SETTINGS.get(remainder) + if field is None: + continue + memory_config[field] = convert_env_value(value) + + return config + + + +# Cached singleton store. Avoids reconstructing an S3Client's boto3 client +# (credential resolution + connection pool setup) on every call — see +# get_memory_store() for details. Filesystem stores are cheap to build but +# there's no reason not to reuse them too. +_store: Storage | None = None +_store_built = False + + +def get_memory_store(settings: Settings) -> Storage | None: + """Return the cached Storage backend for Hippocampus memory, or None if disabled. + + The store is built once and cached (module-level singleton). This matters + most for the S3 backend: constructing ``S3Client`` creates a boto3 client, + which resolves credentials and sets up a connection pool — work we don't + want repeated on every scope/signature call. Filesystem stores are cheap + to build, but caching them too keeps the function's behaviour uniform. + + Call :func:`reset_memory_store` after changing settings (e.g. via + ``reload_settings``) to force a rebuild on next access. + + Filesystem backend: creates a ``FileSystem`` rooted at the resolved + ``memory.root`` path (``~`` is expanded). + + S3 backend: creates an ``S3Client`` pointing at ``memory.s3_bucket`` with + optional region / endpoint overrides. Returns None if no bucket is configured. + + Args: + settings: Application settings. + + Returns: + Cached Storage instance, or None if storage is not configured. + """ + global _store, _store_built + if _store_built: + return _store + + mem = settings.memory + + if mem.backend == "s3": + if not mem.s3_bucket: + _store = None + else: + from codespy.tools.storage.s3.client import S3Client + + _store = S3Client( + bucket=mem.s3_bucket, + region=mem.s3_region or settings.aws_region, + endpoint_url=mem.s3_endpoint_url or None, + ) + else: + # Filesystem (default) + from pathlib import Path + + from codespy.tools.storage.filesystem.client import FileSystem + + root = str(Path(mem.root).expanduser().resolve()) + _store = FileSystem(root) + + _store_built = True + return _store + + +def reset_memory_store() -> None: + """Clear the cached memory store so it is rebuilt on next access. + + Call this after reloading settings (e.g. ``reload_settings()``) so a + changed ``memory`` configuration takes effect. + """ + global _store, _store_built + _store = None + _store_built = False + + +def verify_memory_access(settings: Settings) -> tuple[bool, str]: + """Verify memory storage is accessible when memory is active. + + Returns: + Tuple of (success, message). Success is True when memory is disabled + (no active signatures use it) or when the storage backend responds. + """ + from codespy.config_dspy import SIGNATURE_NAMES + + # Skip if no enabled signature uses memory + if not any( + settings.is_signature_enabled(sig) and settings.get_memory_enabled(sig) + for sig in SIGNATURE_NAMES + ): + return True, "Memory disabled — skipping storage check" + + store = get_memory_store(settings) + if store is None: + return False, "Memory is enabled but storage is not configured (missing S3 bucket?)" + + try: + store.verify_access() + except Exception as e: + return False, f"Memory storage not accessible: {e}" + + return True, f"Memory storage verified ({settings.memory.backend})" diff --git a/src/codespy/tools/__init__.py b/src/codespy/tools/__init__.py index 56935cc..d5a6511 100644 --- a/src/codespy/tools/__init__.py +++ b/src/codespy/tools/__init__.py @@ -1,15 +1,15 @@ """Tools for code parsing, Git platform integration, filesystem operations, web browsing, and security scanning.""" from codespy.tools.cyber import OSVClient, ScanResult, ScanSummary, Vulnerability -from codespy.tools.filesystem import FileSystem from codespy.tools.git import ( ChangedFile, GitClient, - MergeRequest, + PullRequest, detect_platform, get_client, ) from codespy.tools.parsers import RipgrepSearch, SearchResult, TreeSitterParser +from codespy.tools.storage import FileSystem, S3Client, Storage from codespy.tools.web import SearchResults, WebBrowser, WebPage # Note: GitReporter is not exported here to avoid circular imports. @@ -17,11 +17,13 @@ __all__ = [ "FileSystem", + "S3Client", + "Storage", "GitClient", "get_client", "detect_platform", "ChangedFile", - "MergeRequest", + "PullRequest", "OSVClient", "Vulnerability", "ScanResult", diff --git a/src/codespy/tools/cyber/__init__.py b/src/codespy/tools/cyber/__init__.py index a16b662..443f2a0 100644 --- a/src/codespy/tools/cyber/__init__.py +++ b/src/codespy/tools/cyber/__init__.py @@ -14,4 +14,4 @@ "ScanSummary", "Vulnerability", "osv_mcp", -] \ No newline at end of file +] diff --git a/src/codespy/tools/cyber/osv/__init__.py b/src/codespy/tools/cyber/osv/__init__.py index 8c3c9d5..9643221 100644 --- a/src/codespy/tools/cyber/osv/__init__.py +++ b/src/codespy/tools/cyber/osv/__init__.py @@ -1,7 +1,6 @@ """OSV (Open Source Vulnerabilities) API integration for codespy.""" from codespy.tools.cyber.osv.client import OSVClient -from codespy.tools.cyber.osv.server import mcp as osv_mcp from codespy.tools.cyber.osv.models import ( AffectedPackage, BatchQueryResponse, @@ -22,6 +21,7 @@ VulnerabilityQuery, VulnerabilityResponse, ) +from codespy.tools.cyber.osv.server import mcp as osv_mcp __all__ = [ # Client @@ -51,4 +51,4 @@ "Ecosystem", "SeverityType", "ReferenceType", -] \ No newline at end of file +] diff --git a/src/codespy/tools/cyber/osv/client.py b/src/codespy/tools/cyber/osv/client.py index eed3791..a8d560d 100644 --- a/src/codespy/tools/cyber/osv/client.py +++ b/src/codespy/tools/cyber/osv/client.py @@ -7,7 +7,6 @@ from codespy.tools.cyber.osv.models import ( BatchQueryResponse, - BatchQueryResult, PackageQuery, ScanResult, ScanSummary, @@ -336,7 +335,7 @@ def scan_dependencies( try: batch_response = self.query_batch(dependencies) - for i, (dep, query_result) in enumerate( + for _i, (dep, query_result) in enumerate( zip(dependencies, batch_response.results, strict=False) ): result = ScanResult( @@ -448,4 +447,4 @@ def scan_cargo_package(self, name: str, version: str) -> ScanResult: Returns: ScanResult with vulnerabilities found """ - return self.scan_package(name, "crates.io", version) \ No newline at end of file + return self.scan_package(name, "crates.io", version) diff --git a/src/codespy/tools/cyber/osv/models.py b/src/codespy/tools/cyber/osv/models.py index 5c8c1a9..5abb312 100644 --- a/src/codespy/tools/cyber/osv/models.py +++ b/src/codespy/tools/cyber/osv/models.py @@ -1,12 +1,12 @@ """Models for OSV (Open Source Vulnerabilities) API.""" from datetime import datetime -from enum import Enum +from enum import StrEnum from pydantic import BaseModel, Field -class Ecosystem(str, Enum): +class Ecosystem(StrEnum): """Supported package ecosystems in OSV.""" GO = "Go" @@ -38,7 +38,7 @@ class Ecosystem(str, Enum): WOLFI = "Wolfi" -class SeverityType(str, Enum): +class SeverityType(StrEnum): """Severity scoring systems.""" CVSS_V2 = "CVSS_V2" @@ -46,7 +46,7 @@ class SeverityType(str, Enum): CVSS_V4 = "CVSS_V4" -class ReferenceType(str, Enum): +class ReferenceType(StrEnum): """Types of references in vulnerability records.""" ADVISORY = "ADVISORY" @@ -332,4 +332,4 @@ def to_markdown(self) -> str: lines.append(result.to_markdown()) lines.append("\n---\n") - return "\n".join(lines) \ No newline at end of file + return "\n".join(lines) diff --git a/src/codespy/tools/cyber/osv/server.py b/src/codespy/tools/cyber/osv/server.py index db212f9..f37dd4c 100644 --- a/src/codespy/tools/cyber/osv/server.py +++ b/src/codespy/tools/cyber/osv/server.py @@ -228,6 +228,6 @@ def scan_cargo_package(name: str, version: str) -> dict[str, Any]: # Suppress noisy MCP server "Processing request" logs logging.getLogger("mcp.server").setLevel(logging.WARNING) logging.getLogger("mcp.server.lowlevel").setLevel(logging.WARNING) - + _client = OSVClient() mcp.run() diff --git a/src/codespy/tools/filesystem/__init__.py b/src/codespy/tools/filesystem/__init__.py deleted file mode 100644 index 50fd010..0000000 --- a/src/codespy/tools/filesystem/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -"""FileSystem module for file operations.""" - -from codespy.tools.filesystem.client import FileSystem -from codespy.tools.filesystem.models import ( - DirectoryEntry, - DirectoryListing, - EntryType, - FileContent, - FileInfo, - TreeNode, -) - -__all__ = [ - "FileSystem", - "DirectoryEntry", - "DirectoryListing", - "EntryType", - "FileContent", - "FileInfo", - "TreeNode", -] \ No newline at end of file diff --git a/src/codespy/tools/git/__init__.py b/src/codespy/tools/git/__init__.py index 78327a3..853814f 100644 --- a/src/codespy/tools/git/__init__.py +++ b/src/codespy/tools/git/__init__.py @@ -2,13 +2,13 @@ from codespy.tools.git.base import GitClient from codespy.tools.git.client import detect_platform, get_client, is_supported_url -from codespy.tools.git.local_diff import build_mr_from_diff +from codespy.tools.git.local_diff import build_pr_from_diff from codespy.tools.git.models import ( CallerInfo, ChangedFile, FileStatus, GitPlatform, - MergeRequest, + PullRequest, ReviewContext, should_review_file, ) @@ -21,9 +21,9 @@ "get_client", "detect_platform", "is_supported_url", - "build_mr_from_diff", + "build_pr_from_diff", "GitPlatform", - "MergeRequest", + "PullRequest", "ChangedFile", "FileStatus", "ReviewContext", diff --git a/src/codespy/tools/git/base.py b/src/codespy/tools/git/base.py index 8b93d07..b2350e9 100644 --- a/src/codespy/tools/git/base.py +++ b/src/codespy/tools/git/base.py @@ -6,7 +6,7 @@ if TYPE_CHECKING: from codespy.config import Settings - from codespy.tools.git.models import MergeRequest + from codespy.tools.git.models import PullRequest class GitClient(ABC): @@ -24,13 +24,13 @@ def __init__(self, settings: "Settings | None" = None) -> None: @abstractmethod def parse_url(self, url: str) -> tuple[str, str, int]: - """Parse a merge request URL into owner, repo, and MR number. + """Parse a pull request URL into owner, repo, and PR number. Args: - url: Merge request URL + url: Pull request URL Returns: - Tuple of (owner, repo, mr_number) + Tuple of (owner, repo, pr_number) Raises: ValueError: If URL is not valid for this platform @@ -38,14 +38,14 @@ def parse_url(self, url: str) -> tuple[str, str, int]: ... @abstractmethod - def fetch_merge_request(self, url: str) -> "MergeRequest": - """Fetch merge request data from the platform. + def fetch_pull_request(self, url: str) -> "PullRequest": + """Fetch pull request data from the platform. Args: - url: Merge request URL + url: Pull request URL Returns: - MergeRequest model with all data + PullRequest model with all data """ ... @@ -82,10 +82,10 @@ def submit_review( comments: list[dict] | None = None, commit_sha: str | None = None, ) -> None: - """Submit a review on a merge request. + """Submit a review on a pull request. Args: - url: Merge request URL + url: Pull request URL body: Review body/summary text comments: List of inline comment dicts with keys: - path: File path @@ -112,4 +112,4 @@ def can_handle(url: str) -> bool: Returns: True if this client can handle the URL """ - ... \ No newline at end of file + ... diff --git a/src/codespy/tools/git/client.py b/src/codespy/tools/git/client.py index f25ddb9..d90b6ec 100644 --- a/src/codespy/tools/git/client.py +++ b/src/codespy/tools/git/client.py @@ -26,7 +26,7 @@ def get_client(url: str, settings: "Settings | None" = None) -> GitClient: and returns the appropriate client instance. Args: - url: Merge request URL (GitHub PR or GitLab MR) + url: Pull request URL (GitHub PR or GitLab MR) settings: Application settings. Uses global settings if not provided. Returns: @@ -53,7 +53,7 @@ def detect_platform(url: str) -> str: """Detect the Git platform from a URL. Args: - url: Merge request URL + url: Pull request URL Returns: Platform name ('github' or 'gitlab') @@ -77,4 +77,4 @@ def is_supported_url(url: str) -> bool: Returns: True if URL is supported, False otherwise """ - return any(client_class.can_handle(url) for client_class in _CLIENT_CLASSES) \ No newline at end of file + return any(client_class.can_handle(url) for client_class in _CLIENT_CLASSES) diff --git a/src/codespy/tools/git/github_client.py b/src/codespy/tools/git/github_client.py index 5452319..039cdbb 100644 --- a/src/codespy/tools/git/github_client.py +++ b/src/codespy/tools/git/github_client.py @@ -13,7 +13,7 @@ ChangedFile, FileStatus, GitPlatform, - MergeRequest, + PullRequest, ) logger = logging.getLogger(__name__) @@ -72,14 +72,14 @@ def parse_url(self, url: str) -> tuple[str, str, int]: ) return match.group("owner"), match.group("repo"), int(match.group("number")) - def fetch_merge_request(self, url: str) -> MergeRequest: + def fetch_pull_request(self, url: str) -> PullRequest: """Fetch pull request data from GitHub. Args: url: GitHub PR URL Returns: - MergeRequest model with all data + PullRequest model with all data """ owner, repo_name, pr_number = self.parse_url(url) @@ -102,7 +102,7 @@ def fetch_merge_request(self, url: str) -> MergeRequest: ) ) - return MergeRequest( + return PullRequest( number=gh_pr.number, title=gh_pr.title, body=gh_pr.body, @@ -116,6 +116,7 @@ def fetch_merge_request(self, url: str) -> MergeRequest: updated_at=gh_pr.updated_at, repo_owner=owner, repo_name=repo_name, + host="github.com", changed_files=changed_files, labels=[label.name for label in gh_pr.labels], platform=GitPlatform.GITHUB, diff --git a/src/codespy/tools/git/gitlab_client.py b/src/codespy/tools/git/gitlab_client.py index 38e782a..cf17f77 100644 --- a/src/codespy/tools/git/gitlab_client.py +++ b/src/codespy/tools/git/gitlab_client.py @@ -12,7 +12,7 @@ ChangedFile, FileStatus, GitPlatform, - MergeRequest, + PullRequest, ) logger = logging.getLogger(__name__) @@ -65,7 +65,7 @@ def parse_url(self, url: str) -> tuple[str, str, int]: url: GitLab MR URL Returns: - Tuple of (namespace, project, mr_number) + Tuple of (namespace, project, pr_number) Note: namespace may contain slashes for nested groups Raises: @@ -79,7 +79,7 @@ def parse_url(self, url: str) -> tuple[str, str, int]: ) path = match.group("path") - mr_number = int(match.group("number")) + pr_number = int(match.group("number")) # Split path into namespace and project # Handle nested namespaces (e.g., group/subgroup/project) @@ -90,7 +90,7 @@ def parse_url(self, url: str) -> tuple[str, str, int]: namespace = "" project = parts[0] - return namespace, project, mr_number + return namespace, project, pr_number def _get_project_path(self, url: str) -> str: """Get the full project path from URL.""" @@ -99,6 +99,13 @@ def _get_project_path(self, url: str) -> str: raise ValueError(f"Invalid GitLab MR URL: {url}") return match.group("path") + def _get_host(self, url: str) -> str: + """Get the host (e.g. 'gitlab.com' or a self-hosted domain) from URL.""" + match = self.MR_URL_PATTERN.match(url) + if not match: + raise ValueError(f"Invalid GitLab MR URL: {url}") + return match.group("host") + def _map_status(self, diff_status: str) -> FileStatus: """Map GitLab diff status to FileStatus enum.""" status_map = { @@ -108,21 +115,22 @@ def _map_status(self, diff_status: str) -> FileStatus: } return status_map.get(diff_status, FileStatus.MODIFIED) - def fetch_merge_request(self, url: str) -> MergeRequest: + def fetch_pull_request(self, url: str) -> PullRequest: """Fetch merge request data from GitLab. Args: url: GitLab MR URL Returns: - MergeRequest model with all data + PullRequest model with all data """ - namespace, project_name, mr_number = self.parse_url(url) + namespace, project_name, pr_number = self.parse_url(url) project_path = self._get_project_path(url) + host = self._get_host(url) # Get project and MR project = self.gitlab_client.projects.get(project_path) - gl_mr = project.mergerequests.get(mr_number) + gl_mr = project.mergerequests.get(pr_number) # Get diff/changes changes = gl_mr.changes() @@ -160,7 +168,7 @@ def fetch_merge_request(self, url: str) -> MergeRequest: state_map = {"opened": "open", "closed": "closed", "merged": "merged"} state = state_map.get(gl_mr.state, gl_mr.state) - return MergeRequest( + return PullRequest( number=gl_mr.iid, title=gl_mr.title, body=gl_mr.description, @@ -174,6 +182,7 @@ def fetch_merge_request(self, url: str) -> MergeRequest: updated_at=gl_mr.updated_at, repo_owner=namespace, repo_name=project_name, + host=host, changed_files=changed_files, labels=gl_mr.labels, platform=GitPlatform.GITLAB, @@ -283,10 +292,10 @@ def submit_review( commit_sha: Commit SHA to review (defaults to head SHA) """ project_path = self._get_project_path(url) - _, _, mr_number = self.parse_url(url) + _, _, pr_number = self.parse_url(url) project = self.gitlab_client.projects.get(project_path) - gl_mr = project.mergerequests.get(mr_number) + gl_mr = project.mergerequests.get(pr_number) # Get changes and commit SHA for positioning changes = gl_mr.changes() @@ -378,11 +387,11 @@ def submit_review( # Post main review body as a note (with any failed comments appended) if final_body: gl_mr.notes.create({"body": final_body}) - logger.info(f"Posted review on {project_path}!{mr_number}") + logger.info(f"Posted review on {project_path}!{pr_number}") if comments: logger.info( - f"Submitted {successful_count}/{len(comments)} inline comments on {project_path}!{mr_number}" + f"Submitted {successful_count}/{len(comments)} inline comments on {project_path}!{pr_number}" ) def _append_comments_to_body(self, body: str, comments: list[dict]) -> str: diff --git a/src/codespy/tools/git/local_diff.py b/src/codespy/tools/git/local_diff.py index cc5856a..b6a26d3 100644 --- a/src/codespy/tools/git/local_diff.py +++ b/src/codespy/tools/git/local_diff.py @@ -1,11 +1,11 @@ -"""Build MergeRequest objects from local git state (no GitHub/GitLab needed).""" +"""Build PullRequest objects from local git state (no GitHub/GitLab needed).""" import logging import subprocess -from datetime import datetime +from datetime import UTC, datetime from pathlib import Path -from codespy.tools.git.models import ChangedFile, FileStatus, GitPlatform, MergeRequest +from codespy.tools.git.models import ChangedFile, FileStatus, GitPlatform, PullRequest logger = logging.getLogger(__name__) @@ -49,33 +49,39 @@ def _count_diff_lines(patch: str) -> tuple[int, int]: return additions, deletions -def _get_repo_info(repo_path: Path) -> tuple[str, str]: - """Extract owner and repo name from git remote or directory name. +def _get_repo_info(repo_path: Path) -> tuple[str, str, str]: + """Extract host, owner, and repo name from git remote or directory name. Returns: - Tuple of (owner, repo_name) + Tuple of (host, owner, repo_name). ``host`` is empty when it cannot + be determined (e.g. no ``origin`` remote configured). """ try: remote_url = _run_git(repo_path, "remote", "get-url", "origin") # Handle SSH: git@github.com:owner/repo.git if remote_url.startswith("git@"): - path_part = remote_url.split(":", 1)[1] + host_part, path_part = remote_url.split(":", 1) + host = host_part.split("@", 1)[-1] # Handle HTTPS: https://github.com/owner/repo.git elif "://" in remote_url: - path_part = remote_url.split("://", 1)[1].split("/", 1)[1] + after_scheme = remote_url.split("://", 1)[1] + host, path_part = after_scheme.split("/", 1) + # Strip any embedded credentials (e.g. user@host or token@host) + host = host.split("@")[-1] else: + host = "" path_part = remote_url # Remove .git suffix path_part = path_part.removesuffix(".git") parts = path_part.strip("/").split("/") if len(parts) >= 2: - return parts[-2], parts[-1] + return host, parts[-2], parts[-1] except (RuntimeError, IndexError, ValueError): pass - # Fallback to directory name - return "local", repo_path.name + # Fallback to directory name, no known host + return "", "local", repo_path.name def _get_current_branch(repo_path: Path) -> str: @@ -94,12 +100,12 @@ def _get_current_user(repo_path: Path) -> str: return "local-user" -def build_mr_from_diff( +def build_pr_from_diff( repo_path: Path, base_ref: str = "main", include_uncommitted: bool = False, -) -> MergeRequest: - """Build a MergeRequest from local git diff. +) -> PullRequest: + """Build a PullRequest from local git diff. Args: repo_path: Path to the local git repository @@ -108,7 +114,7 @@ def build_mr_from_diff( If False, diff current branch against base_ref. Returns: - A MergeRequest object representing the local changes + A PullRequest object representing the local changes Raises: RuntimeError: If git commands fail @@ -118,7 +124,7 @@ def build_mr_from_diff( if not (repo_path / ".git").exists(): raise FileNotFoundError(f"Not a git repository: {repo_path}") - owner, repo_name = _get_repo_info(repo_path) + host, owner, repo_name = _get_repo_info(repo_path) head_branch = _get_current_branch(repo_path) author = _get_current_user(repo_path) @@ -147,7 +153,7 @@ def build_mr_from_diff( name_status_output = _run_git(repo_path, "diff", "--name-status", diff_ref) if not name_status_output: logger.info("No changes found") - return MergeRequest( + return PullRequest( number=0, title=title, body=f"Local diff: {diff_ref}...HEAD", @@ -157,10 +163,11 @@ def build_mr_from_diff( head_branch=head_branch, base_sha=base_sha, head_sha=head_sha, - created_at=datetime.utcnow(), - updated_at=datetime.utcnow(), + created_at=datetime.now(UTC), + updated_at=datetime.now(UTC), repo_owner=owner, repo_name=repo_name, + host=host, changed_files=[], platform=GitPlatform.GITHUB, # Doesn't matter for local review ) @@ -198,9 +205,9 @@ def build_mr_from_diff( previous_filename=previous_filename, )) - logger.info(f"Built local MR with {len(changed_files)} changed files") + logger.info(f"Built local PR with {len(changed_files)} changed files") - return MergeRequest( + return PullRequest( number=0, title=title, body=f"Local diff: {diff_ref}...HEAD in {repo_path}", @@ -210,10 +217,11 @@ def build_mr_from_diff( head_branch=head_branch, base_sha=base_sha, head_sha=head_sha, - created_at=datetime.utcnow(), - updated_at=datetime.utcnow(), + created_at=datetime.now(UTC), + updated_at=datetime.now(UTC), repo_owner=owner, repo_name=repo_name, + host=host, changed_files=changed_files, platform=GitPlatform.GITHUB, # Doesn't matter for local review ) diff --git a/src/codespy/tools/git/models.py b/src/codespy/tools/git/models.py index 6b397fe..79cec59 100644 --- a/src/codespy/tools/git/models.py +++ b/src/codespy/tools/git/models.py @@ -1,14 +1,14 @@ -"""Data models for Git merge requests (GitHub PRs and GitLab MRs).""" +"""Data models for Git pull requests (GitHub PRs and GitLab MRs).""" import re from datetime import datetime -from enum import Enum +from enum import StrEnum from pydantic import BaseModel, Field -class FileStatus(str, Enum): - """Status of a file in a merge request.""" +class FileStatus(StrEnum): + """Status of a file in a pull request.""" ADDED = "added" MODIFIED = "modified" @@ -16,7 +16,7 @@ class FileStatus(str, Enum): RENAMED = "renamed" -class GitPlatform(str, Enum): +class GitPlatform(StrEnum): """Supported Git platforms.""" GITHUB = "github" @@ -59,7 +59,7 @@ class GitPlatform(str, Enum): class ChangedFile(BaseModel): - """Represents a file changed in a merge request.""" + """Represents a file changed in a pull request.""" filename: str = Field(description="Path to the file") status: FileStatus = Field(description="Type of change (added, modified, removed, renamed)") @@ -135,7 +135,7 @@ def is_source_map(self) -> bool: def is_in_excluded_directory(self, excluded_directories: list[str]) -> bool: """Check if this file is in an excluded directory. - + Args: excluded_directories: List of directory names to exclude (from settings) """ @@ -146,11 +146,11 @@ def is_in_excluded_directory(self, excluded_directories: list[str]) -> bool: @property def valid_new_line_numbers(self) -> set[int]: """Get line numbers in the new file that are valid for inline comments. - + Parses the unified diff patch to extract line numbers where inline comments can be placed. Only lines that appear in the diff (additions and context lines) are valid for GitHub/GitLab review comments. - + Returns: Set of valid line numbers in the new version of the file """ @@ -172,11 +172,7 @@ def valid_new_line_numbers(self) -> set[int]: continue # Context line (unchanged) - valid for comments - if line.startswith(" "): - valid_lines.add(current_new_line) - current_new_line += 1 - # Addition line - valid for comments - elif line.startswith("+"): + if line.startswith(" ") or line.startswith("+"): valid_lines.add(current_new_line) current_new_line += 1 # Deletion line - doesn't increment new line counter (not in new file) @@ -190,10 +186,10 @@ def valid_new_line_numbers(self) -> set[int]: def is_line_in_diff(self, line_number: int) -> bool: """Check if a line number is valid for inline comments. - + Args: line_number: Line number to check - + Returns: True if the line is part of the diff and can receive inline comments """ @@ -202,11 +198,11 @@ def is_line_in_diff(self, line_number: int) -> bool: def should_review_file(file: ChangedFile, excluded_directories: list[str]) -> bool: """Check if a file should be included in code review. - + Args: file: The ChangedFile to check excluded_directories: List of directory names to exclude (from settings) - + Returns: True if file should be reviewed, False if it should be skipped """ @@ -218,31 +214,36 @@ def should_review_file(file: ChangedFile, excluded_directories: list[str]) -> bo return False if file.is_source_map: return False - if file.is_in_excluded_directory(excluded_directories): - return False - return True + return not file.is_in_excluded_directory(excluded_directories) -class MergeRequest(BaseModel): - """Represents a merge request (GitHub PR or GitLab MR).""" +class PullRequest(BaseModel): + """Represents a pull request (GitHub PR or GitLab MR).""" - number: int = Field(description="MR/PR number") - title: str = Field(description="MR/PR title") - body: str | None = Field(default=None, description="MR/PR description/body") - state: str = Field(description="MR/PR state (open, closed, merged)") - author: str = Field(description="MR/PR author username") + number: int = Field(description="PR number") + title: str = Field(description="PR title") + body: str | None = Field(default=None, description="PR description/body") + state: str = Field(description="PR state (open, closed, merged)") + author: str = Field(description="PR author username") base_branch: str = Field(description="Target branch") head_branch: str = Field(description="Source branch") base_sha: str = Field(description="Base commit SHA") head_sha: str = Field(description="Head commit SHA") - created_at: datetime = Field(description="MR/PR creation timestamp") - updated_at: datetime = Field(description="MR/PR last update timestamp") + created_at: datetime = Field(description="PR creation timestamp") + updated_at: datetime = Field(description="PR last update timestamp") repo_owner: str = Field(description="Repository owner/namespace") repo_name: str = Field(description="Repository name") + host: str = Field( + default="", + description=( + "Repo host (e.g. 'github.com', 'gitlab.example.com'). " + "Empty when unknown (e.g. local repo with no git remote)." + ), + ) changed_files: list[ChangedFile] = Field( default_factory=list, description="List of changed files" ) - labels: list[str] = Field(default_factory=list, description="MR/PR labels") + labels: list[str] = Field(default_factory=list, description="PR labels") platform: GitPlatform = Field(description="Git platform (github, gitlab)") @property @@ -250,9 +251,21 @@ def repo_full_name(self) -> str: """Get full repository name (owner/repo).""" return f"{self.repo_owner}/{self.repo_name}" + @property + def repo_slug(self) -> str: + """Get the host-qualified repository identifier (e.g. 'github.com/owner/repo'). + + Falls back to ``repo_full_name`` (no host prefix) when ``host`` is + unknown — e.g. a local repository with no git remote configured. + Used by Hippocampus memory to build a stable, host-qualified episode + path so local and remote reviews of the same repository share memory. + """ + base = self.repo_full_name + return f"{self.host}/{base}" if self.host else base + @property def url(self) -> str: - """Get the MR/PR URL.""" + """Get the PR URL.""" if self.platform == GitPlatform.GITLAB: return f"https://gitlab.com/{self.repo_full_name}/-/merge_requests/{self.number}" return f"https://github.com/{self.repo_full_name}/pull/{self.number}" @@ -268,10 +281,6 @@ def code_files(self) -> list[ChangedFile]: return [f for f in self.changed_files if f.is_code_file] -# Alias for backward compatibility -PullRequest = MergeRequest - - class CallerInfo(BaseModel): """Information about a caller of a function/method.""" @@ -284,7 +293,7 @@ class CallerInfo(BaseModel): class ReviewContext(BaseModel): """Context information for code review.""" - merge_request: MergeRequest = Field(description="The merge request being reviewed") + pull_request: PullRequest = Field(description="The pull request being reviewed") related_files: dict[str, str] = Field( default_factory=dict, description="Related files content (imports, dependencies)", @@ -297,12 +306,6 @@ class ReviewContext(BaseModel): description="Callers of changed functions, keyed by filename", ) - # Alias for backward compatibility - @property - def pull_request(self) -> MergeRequest: - """Alias for merge_request (backward compatibility).""" - return self.merge_request - def get_context_for_file(self, filename: str) -> str: """Get context string for a specific file.""" context_parts = [] @@ -344,4 +347,4 @@ def get_callers_for_file(self, filename: str) -> str: if len(func_callers) > 10: lines.append(f" ... and {len(func_callers) - 10} more callers") - return "\n".join(lines) \ No newline at end of file + return "\n".join(lines) diff --git a/src/codespy/tools/git/patch_utils.py b/src/codespy/tools/git/patch_utils.py new file mode 100644 index 0000000..fd16504 --- /dev/null +++ b/src/codespy/tools/git/patch_utils.py @@ -0,0 +1,431 @@ +"""Patch compaction utilities to expand diff context to function boundaries.""" + +from __future__ import annotations + +import logging +import re +from pathlib import Path +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from codespy.agents.reviewer.models import ScopeResult + +from codespy.tools.git.models import ChangedFile, FileStatus +from codespy.tools.parsers.treesitter import FunctionInfo, TreeSitterParser + +logger = logging.getLogger(__name__) + +# Non-code file extensions that should not be processed +NON_CODE_EXTENSIONS = { + "md", + "txt", + "rst", + "yaml", + "yml", + "json", + "toml", + "ini", + "cfg", + "conf", + "xml", + "html", + "htm", + "css", + "scss", + "sass", + "less", + "csv", + "tsv", +} + +# Compiled regex for hunk header parsing +HUNK_HEADER_RE = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@") + + +def compact_patches(scopes: list[ScopeResult], repo_path: Path) -> None: + """Compact patches on all changed files across scopes (mutates in place). + + For each code file with a patch, attempts compaction. Non-code files + and files where compaction fails keep their original patch. + + Args: + scopes: List of scope results containing changed files + repo_path: Path to the cloned repository + """ + parser = TreeSitterParser(repo_path) + + for scope in scopes: + for file in scope.changed_files: + if not _should_compact_file(file): + continue + + try: + if file.patch is None: + continue + compacted = compact_patch(file.patch, Path(file.filename), repo_path, parser) + if compacted != file.patch: + logger.debug(f"Compacted patch for {file.filename}") + file.patch = compacted + except Exception as e: + logger.debug(f"Failed to compact patch for {file.filename}: {e}") + # Keep original patch on failure + + +def _should_compact_file(file: ChangedFile) -> bool: + """Check if a file should be compacted. + + Args: + file: The ChangedFile to check + + Returns: + True if the file should be compacted + """ + # Skip files without patches + if not file.patch: + return False + + # Skip deleted files (no source to read) + if file.status == FileStatus.REMOVED: + return False + + # Skip non-code files + if file.extension in NON_CODE_EXTENSIONS: + return False + + # Skip binary and lock files + return not (file.is_binary or file.is_lock_file) + + +def compact_patch( + raw_patch: str, + file_path: Path, + repo_path: Path, + parser: TreeSitterParser | None = None, +) -> str: + """Expand diff context to full function bodies using TreeSitter. + + Returns compacted patch string, or raw_patch unchanged if compaction + isn't possible (no TreeSitter, not a code file, deleted file, etc.). + + Args: + raw_patch: The original unified diff patch + file_path: Path to the file (relative to repo root) + repo_path: Path to the repository root + parser: Optional TreeSitterParser instance (created if not provided) + + Returns: + Compacted patch or original patch if compaction fails + """ + # Skip empty patches + if not raw_patch: + return raw_patch + + # Create parser if not provided + if parser is None: + parser = TreeSitterParser(repo_path) + + # Check if TreeSitter is available + if not parser.available: + logger.debug(f"TreeSitter not available, skipping compaction for {file_path}") + return raw_patch + + # Check if file is a code file we can parse + extension = file_path.suffix.lstrip(".") if file_path.suffix else "" + if not extension or extension in NON_CODE_EXTENSIONS: + return raw_patch + + # Get absolute file path + abs_file_path = repo_path / file_path + if not abs_file_path.exists(): + logger.warning(f"Source file not found: {abs_file_path}") + return raw_patch + + # Find function definitions in the file + try: + functions = parser.find_function_definitions(abs_file_path) + except Exception as e: + logger.debug(f"Failed to find functions in {file_path}: {e}") + return raw_patch + + if not functions: + # No functions found, nothing to expand + return raw_patch + + # Parse hunks from patch + hunks = _parse_hunks(raw_patch) + if not hunks: + return raw_patch + + # Read source file lines + try: + source_lines = abs_file_path.read_text().splitlines() + except Exception as e: + logger.debug(f"Failed to read source file {file_path}: {e}") + return raw_patch + + # Expand each hunk to function boundaries + expanded_hunks: list[dict[str, Any]] = [] + for hunk in hunks: + expanded = _expand_hunk_to_functions(hunk, functions, source_lines) + if expanded: + expanded_hunks.append(expanded) + + if not expanded_hunks: + return raw_patch + + # Merge overlapping hunks + merged_hunks = _merge_hunks(expanded_hunks) + + # Rebuild the compacted patch + compacted = _rebuild_patch(raw_patch, merged_hunks, source_lines) + + return compacted if compacted else raw_patch + + +def _parse_hunks(patch: str) -> list[dict[str, Any]]: + """Parse hunks from a unified diff patch. + + Args: + patch: The unified diff patch string + + Returns: + List of hunk dictionaries with parsed info + """ + hunks = [] + lines = patch.split("\n") + i = 0 + + while i < len(lines): + line = lines[i] + + # Check for hunk header + match = HUNK_HEADER_RE.match(line) + if match: + old_start = int(match.group(1)) + old_count = int(match.group(2)) if match.group(2) else 1 + new_start = int(match.group(3)) + new_count = int(match.group(4)) if match.group(4) else 1 + + # Collect hunk lines + hunk_lines = [] + i += 1 + changed_new_lines = [] + current_new_line = new_start + + while i < len(lines): + hunk_line = lines[i] + + # Stop at next hunk header or empty line that ends the hunk + if hunk_line.startswith("@@"): + break + + hunk_lines.append(hunk_line) + + # Track changed lines in the new file + if hunk_line.startswith("+"): + changed_new_lines.append(current_new_line) + current_new_line += 1 + elif hunk_line.startswith(" "): + current_new_line += 1 + elif hunk_line.startswith("-"): + pass # Deleted line, not in new file + elif hunk_line.startswith("\\"): + # "\ No newline at end of file" marker + pass + + i += 1 + + hunks.append({ + "header": line, + "old_start": old_start, + "old_count": old_count, + "new_start": new_start, + "new_count": new_count, + "lines": hunk_lines, + "changed_new_lines": changed_new_lines, + }) + else: + i += 1 + + return hunks + + +def _expand_hunk_to_functions( + hunk: dict[str, Any], + functions: list[FunctionInfo], + source_lines: list[str], +) -> dict[str, Any] | None: + """Expand a hunk to cover enclosing function boundaries. + + Args: + hunk: The hunk dictionary + functions: List of function definitions + source_lines: Source file lines + + Returns: + Expanded hunk dictionary or None if no expansion needed + """ + changed_lines = hunk["changed_new_lines"] + if not changed_lines: + return hunk + + # Find enclosing functions for changed lines + enclosing_functions: list[FunctionInfo] = [] + for line_num in changed_lines: + # Find innermost enclosing function + best_match: FunctionInfo | None = None + for func in functions: + if func.line_start <= line_num <= func.line_end and (best_match is None or ( + func.line_start >= best_match.line_start + and func.line_end <= best_match.line_end + )): + best_match = func + + if best_match and best_match not in enclosing_functions: + enclosing_functions.append(best_match) + + if not enclosing_functions: + # No enclosing functions, return hunk unchanged + return hunk + + # Calculate expansion range + min_func_start = min(f.line_start for f in enclosing_functions) + max_func_end = max(f.line_end for f in enclosing_functions) + + # Determine hunk boundaries in new file + hunk_start_new = hunk["new_start"] + hunk_end_new = hunk_start_new + hunk["new_count"] - 1 + # Account for lines that don't end with newline + if hunk["lines"] and hunk["lines"][-1].startswith("\\"): + hunk_end_new = hunk_start_new + hunk["new_count"] + + # Don't expand if already covering the entire function(s) + if min_func_start >= hunk_start_new and max_func_end <= hunk_end_new: + return hunk + + # Calculate expansion + expansion_start = min(hunk_start_new, min_func_start) + expansion_end = max(hunk_end_new, max_func_end) + + return { + "original_hunk": hunk, + "expansion_start": expansion_start, + "expansion_end": expansion_end, + "hunk_start_new": hunk_start_new, + "hunk_end_new": hunk_end_new, + "enclosing_functions": enclosing_functions, + } + + +def _merge_hunks(expanded_hunks: list[dict[str, Any]]) -> list[dict[str, Any]]: + """Merge overlapping or adjacent expanded hunks. + + Args: + expanded_hunks: List of expanded hunk dictionaries + + Returns: + List of merged hunks + """ + if len(expanded_hunks) <= 1: + return expanded_hunks + + # Sort by expansion start (fallback to new_start for non-expanded hunks) + sorted_hunks = sorted(expanded_hunks, key=lambda h: h.get("expansion_start", h.get("new_start", 0))) + + merged = [sorted_hunks[0]] + + for hunk in sorted_hunks[1:]: + last = merged[-1] + + # Check if this hunk overlaps or is adjacent to the last merged hunk + last_end = last.get("expansion_end", 0) + current_start = hunk.get("expansion_start", 0) + + # Only merge expanded hunks (those with expansion boundaries) + can_merge = "expansion_start" in last and "expansion_start" in hunk + if can_merge and current_start <= last_end + 1: # Overlapping or adjacent + # Merge: extend the end if needed + last["expansion_end"] = max(last_end, hunk.get("expansion_end", 0)) + # Keep track of original hunks for reconstruction + if "original_hunk" in last and "original_hunk" in hunk: + if "merged_hunks" not in last: + last["merged_hunks"] = [last["original_hunk"]] + last["merged_hunks"].append(hunk["original_hunk"]) + else: + merged.append(hunk) + + return merged + + +def _build_expanded_hunk( + merged_hunk: dict[str, Any], + source_lines: list[str], +) -> tuple[str, list[str]]: + """Build the header and content lines for one expanded hunk. + + Returns: + Tuple of (hunk_header, hunk_lines). + """ + expansion_start = merged_hunk.get("expansion_start") + expansion_end = merged_hunk.get("expansion_end") + original_hunk = merged_hunk.get("original_hunk", merged_hunk) + + if expansion_start is None or expansion_end is None: + return original_hunk["header"], original_hunk["lines"] + + hunk_start_new = merged_hunk.get("hunk_start_new", expansion_start) + hunk_end_new = merged_hunk.get("hunk_end_new", expansion_end) + + new_hunk_lines: list[str] = [] + + # Pre-context + for line_num in range(expansion_start, hunk_start_new): + if line_num <= len(source_lines): + new_hunk_lines.append(f" {source_lines[line_num - 1]}") + + # Diff lines from constituent hunks + for sub_hunk in merged_hunk.get("merged_hunks", [original_hunk]): + new_hunk_lines.extend(sub_hunk.get("lines", [])) + + # Post-context + for line_num in range(hunk_end_new + 1, expansion_end + 1): + if line_num <= len(source_lines): + new_hunk_lines.append(f" {source_lines[line_num - 1]}") + + # Compute counts + new_file_count = sum(1 for l in new_hunk_lines if l.startswith((" ", "+"))) + old_count = sum(1 for l in new_hunk_lines if l.startswith((" ", "-"))) + header = f"@@ -{expansion_start},{old_count} +{expansion_start},{new_file_count} @@" + + return header, new_hunk_lines + + +def _rebuild_patch( + raw_patch: str, + merged_hunks: list[dict[str, Any]], + source_lines: list[str], +) -> str | None: + """Rebuild the patch with expanded context. + + Args: + raw_patch: The original patch + merged_hunks: List of merged expanded hunks + source_lines: Source file lines + + Returns: + Rebuilt patch string or None if rebuild fails + """ + if not merged_hunks: + return None + lines = raw_patch.split("\n") + header_lines: list[str] = [] + for line in lines: + if line.startswith("@@"): + break + header_lines.append(line) + result_lines: list[str] = list(header_lines) + for merged_hunk in merged_hunks: + hunk_header, hunk_lines = _build_expanded_hunk(merged_hunk, source_lines) + result_lines.append(hunk_header) + result_lines.extend(hunk_lines) + return "\n".join(result_lines) diff --git a/src/codespy/tools/git/server.py b/src/codespy/tools/git/server.py index 5533c31..7379780 100644 --- a/src/codespy/tools/git/server.py +++ b/src/codespy/tools/git/server.py @@ -2,13 +2,12 @@ import logging import os -import sys from mcp.server.fastmcp import FastMCP from codespy.config import Settings -from codespy.tools.git.client import get_client, detect_platform, is_supported_url from codespy.tools.git.base import GitClient +from codespy.tools.git.client import detect_platform, get_client, is_supported_url logger = logging.getLogger(__name__) @@ -27,47 +26,47 @@ def _get_client(url: str) -> GitClient: @mcp.tool() -def parse_mr_url(url: str) -> dict: - """Parse a Git merge request URL into owner, repo, and MR number. +def parse_pr_url(url: str) -> dict: + """Parse a Git pull request URL into owner, repo, and PR number. Works with both GitHub Pull Requests and GitLab Merge Requests. Args: - url: Git MR URL + url: Git PR/MR URL - GitHub: https://github.com/owner/repo/pull/123 - GitLab: https://gitlab.com/group/project/-/merge_requests/123 Returns: - Dict with owner (or namespace), repo, mr_number, and platform + Dict with owner (or namespace), repo, pr_number, and platform """ platform = detect_platform(url) client = _get_client(url) - owner, repo, mr_number = client.parse_url(url) + owner, repo, pr_number = client.parse_url(url) return { "owner": owner, "repo": repo, - "mr_number": mr_number, + "pr_number": pr_number, "platform": platform.value, } @mcp.tool() -def fetch_merge_request(mr_url: str) -> dict: - """Fetch merge request data from GitHub or GitLab. +def fetch_pull_request(pr_url: str) -> dict: + """Fetch pull request data from GitHub or GitLab. Works with both GitHub Pull Requests and GitLab Merge Requests. Args: - mr_url: Git MR URL + pr_url: Git PR/MR URL - GitHub: https://github.com/owner/repo/pull/123 - GitLab: https://gitlab.com/group/project/-/merge_requests/123 Returns: - Dict with MR data including title, body, changed files, etc. + Dict with PR data including title, body, changed files, etc. """ - client = _get_client(mr_url) - mr = client.fetch_merge_request(mr_url) - return mr.model_dump() + client = _get_client(pr_url) + pr = client.fetch_pull_request(pr_url) + return pr.model_dump() @mcp.tool() @@ -102,11 +101,11 @@ def clone_repository( Path to the cloned repository """ from pathlib import Path - from codespy.tools.git.models import GitPlatform - + + if _settings is None: raise RuntimeError("Settings not initialized") - + # Build a URL to get the right client if platform.lower() == "gitlab": # Use the configured GitLab URL or default @@ -114,7 +113,7 @@ def clone_repository( dummy_url = f"{gitlab_base}/{owner}/{repo_name}/-/merge_requests/1" else: dummy_url = f"https://github.com/{owner}/{repo_name}/pull/1" - + client = _get_client(dummy_url) logger.info(f"[GIT] {_caller_module} -> clone_repository: {owner}/{repo_name}@{ref[:8]} ({platform})") path = client.clone_repository( @@ -155,7 +154,7 @@ def detect_git_platform(url: str) -> dict: # Suppress noisy MCP server "Processing request" logs logging.getLogger("mcp.server").setLevel(logging.WARNING) logging.getLogger("mcp.server.lowlevel").setLevel(logging.WARNING) - + # Initialize with settings from environment _settings = Settings() - mcp.run() \ No newline at end of file + mcp.run() diff --git a/src/codespy/tools/mcp_utils.py b/src/codespy/tools/mcp_utils.py index 9d81573..e075d4d 100644 --- a/src/codespy/tools/mcp_utils.py +++ b/src/codespy/tools/mcp_utils.py @@ -53,7 +53,7 @@ async def connect_mcp_server( await session.__aenter__() contexts.append(session) await session.initialize() - + tools_response = await session.list_tools() return [dspy.Tool.from_mcp_tool(session, tool) for tool in tools_response.tools] @@ -72,4 +72,4 @@ async def cleanup_mcp_contexts(contexts: list[Any]) -> None: try: await ctx.__aexit__(None, None, None) except Exception as e: - logger.warning(f"Error cleaning up MCP context: {e}") \ No newline at end of file + logger.warning(f"Error cleaning up MCP context: {e}") diff --git a/src/codespy/tools/parsers/ripgrep/__init__.py b/src/codespy/tools/parsers/ripgrep/__init__.py index be52fa7..b4b915a 100644 --- a/src/codespy/tools/parsers/ripgrep/__init__.py +++ b/src/codespy/tools/parsers/ripgrep/__init__.py @@ -2,4 +2,4 @@ from codespy.tools.parsers.ripgrep.client import RipgrepSearch, SearchResult -__all__ = ["RipgrepSearch", "SearchResult"] \ No newline at end of file +__all__ = ["RipgrepSearch", "SearchResult"] diff --git a/src/codespy/tools/parsers/ripgrep/server.py b/src/codespy/tools/parsers/ripgrep/server.py index 1d6ea3d..a7a53ed 100644 --- a/src/codespy/tools/parsers/ripgrep/server.py +++ b/src/codespy/tools/parsers/ripgrep/server.py @@ -187,7 +187,7 @@ def search_literal( # Suppress noisy MCP server "Processing request" logs logging.getLogger("mcp.server").setLevel(logging.WARNING) logging.getLogger("mcp.server.lowlevel").setLevel(logging.WARNING) - + repo_path = sys.argv[1] if len(sys.argv) > 1 else "." _search = RipgrepSearch(repo_path) mcp.run() diff --git a/src/codespy/tools/parsers/treesitter/__init__.py b/src/codespy/tools/parsers/treesitter/__init__.py index 5c0d3e0..01a4c56 100644 --- a/src/codespy/tools/parsers/treesitter/__init__.py +++ b/src/codespy/tools/parsers/treesitter/__init__.py @@ -12,4 +12,4 @@ "FunctionInfo", "CallInfo", "SymbolInfo", -] \ No newline at end of file +] diff --git a/src/codespy/tools/parsers/treesitter/base_extractor.py b/src/codespy/tools/parsers/treesitter/base_extractor.py index 0fab5f0..5605e00 100644 --- a/src/codespy/tools/parsers/treesitter/base_extractor.py +++ b/src/codespy/tools/parsers/treesitter/base_extractor.py @@ -9,7 +9,7 @@ from codespy.tools.parsers.treesitter.models import FunctionInfo if TYPE_CHECKING: - from tree_sitter import Node + pass class BaseExtractor(ABC): @@ -64,4 +64,4 @@ def _get_node_text(self, node: Any, source: bytes) -> str: Returns: Decoded text content """ - return source[node.start_byte:node.end_byte].decode() \ No newline at end of file + return source[node.start_byte:node.end_byte].decode() diff --git a/src/codespy/tools/parsers/treesitter/extractors/__init__.py b/src/codespy/tools/parsers/treesitter/extractors/__init__.py index 88d270a..342c30f 100644 --- a/src/codespy/tools/parsers/treesitter/extractors/__init__.py +++ b/src/codespy/tools/parsers/treesitter/extractors/__init__.py @@ -1,22 +1,34 @@ """Language-specific extractors for tree-sitter parsing.""" +from codespy.tools.parsers.treesitter.extractors.bash import BashExtractor +from codespy.tools.parsers.treesitter.extractors.cpp import CppExtractor +from codespy.tools.parsers.treesitter.extractors.csharp import CSharpExtractor from codespy.tools.parsers.treesitter.extractors.go import GoExtractor from codespy.tools.parsers.treesitter.extractors.java import JavaExtractor from codespy.tools.parsers.treesitter.extractors.javascript import JavaScriptExtractor from codespy.tools.parsers.treesitter.extractors.kotlin import KotlinExtractor from codespy.tools.parsers.treesitter.extractors.objc import ObjCExtractor +from codespy.tools.parsers.treesitter.extractors.php import PHPExtractor from codespy.tools.parsers.treesitter.extractors.python import PythonExtractor +from codespy.tools.parsers.treesitter.extractors.ripgrep_fallback import RipgrepHeuristicsExtractor +from codespy.tools.parsers.treesitter.extractors.ruby import RubyExtractor from codespy.tools.parsers.treesitter.extractors.rust import RustExtractor from codespy.tools.parsers.treesitter.extractors.swift import SwiftExtractor from codespy.tools.parsers.treesitter.extractors.terraform import TerraformExtractor __all__ = [ + "BashExtractor", + "CppExtractor", + "CSharpExtractor", "GoExtractor", "JavaExtractor", "JavaScriptExtractor", "KotlinExtractor", "ObjCExtractor", + "PHPExtractor", "PythonExtractor", + "RipgrepHeuristicsExtractor", + "RubyExtractor", "RustExtractor", "SwiftExtractor", "TerraformExtractor", diff --git a/src/codespy/tools/parsers/treesitter/extractors/bash.py b/src/codespy/tools/parsers/treesitter/extractors/bash.py new file mode 100644 index 0000000..8d02085 --- /dev/null +++ b/src/codespy/tools/parsers/treesitter/extractors/bash.py @@ -0,0 +1,94 @@ +"""Bash/Shell function extractor.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +from codespy.tools.parsers.treesitter.base_extractor import BaseExtractor +from codespy.tools.parsers.treesitter.models import FunctionInfo + + +class BashExtractor(BaseExtractor): + """Extract function definitions from Bash/Shell source code.""" + + def extract_functions( + self, + node: Any, + file_path: Path, + source: bytes, + ) -> list[FunctionInfo]: + """Extract Bash function definitions.""" + functions: list[FunctionInfo] = [] + + def visit(n: Any) -> None: + if n.type == "function_definition": + func_info = self._extract_function_info(n, file_path, source) + if func_info: + functions.append(func_info) + + for child in n.children: + visit(child) + + visit(node) + return functions + + def _extract_function_info( + self, + func_node: Any, + file_path: Path, + source: bytes, + ) -> FunctionInfo | None: + """Extract function info from a function_definition node.""" + name_node = func_node.child_by_field_name("name") + if not name_node: + return None + + name = self._get_node_text(name_node, source) + + # Bash functions don't have typed parameters or return types + # Parameters are accessed via $1, $2, etc. + # We can try to extract parameter count from the body + params = self._extract_bash_params(func_node, source) + + return FunctionInfo( + name=name, + file=str(file_path), + line_start=func_node.start_point[0] + 1, + line_end=func_node.end_point[0] + 1, + parameters=params, + return_type=None, # Bash doesn't have explicit return types + is_method=False, + ) + + def _extract_bash_params(self, node: Any, source: bytes) -> list[str]: + """Extract Bash function parameters from the body. + + Bash uses positional parameters ($1, $2, etc.) so we look for + references to determine how many parameters the function uses. + """ + params: list[str] = [] + seen_params: set[int] = set() + + def find_param_refs(n: Any) -> None: + if n.type == "special_variable_name": + text = self._get_node_text(n, source) + if text.startswith("$"): + try: + param_num = int(text[1:]) + if param_num > 0: + seen_params.add(param_num) + except ValueError: + pass + for child in n.children: + find_param_refs(child) + + find_param_refs(node) + + # Build parameter list based on what we found + if seen_params: + max_param = max(seen_params) + for i in range(1, max_param + 1): + params.append(f"${i}") + + return params diff --git a/src/codespy/tools/parsers/treesitter/extractors/cpp.py b/src/codespy/tools/parsers/treesitter/extractors/cpp.py new file mode 100644 index 0000000..0924c6a --- /dev/null +++ b/src/codespy/tools/parsers/treesitter/extractors/cpp.py @@ -0,0 +1,122 @@ +"""C/C++ function extractor.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +from codespy.tools.parsers.treesitter.base_extractor import BaseExtractor +from codespy.tools.parsers.treesitter.models import FunctionInfo + + +class CppExtractor(BaseExtractor): + """Extract function definitions from C/C++ source code.""" + + def extract_functions( + self, + node: Any, + file_path: Path, + source: bytes, + ) -> list[FunctionInfo]: + """Extract C/C++ function definitions.""" + functions: list[FunctionInfo] = [] + + def visit(n: Any) -> None: + if n.type == "function_definition": + decl_node = n.child_by_field_name("declarator") + if decl_node: + func_info = self._extract_function_info(decl_node, n, file_path, source) + if func_info: + functions.append(func_info) + + for child in n.children: + visit(child) + + visit(node) + return functions + + def _extract_function_info( + self, + declarator: Any, + function_node: Any, + file_path: Path, + source: bytes, + ) -> FunctionInfo | None: + """Extract function info from a function declarator node.""" + # Get function name from declarator + # The declarator can be: function_declarator, identifier, etc. + name_node = None + params = [] + return_type = None + + if declarator.type == "function_declarator": + # Get name from the declarator + name_part = declarator.child_by_field_name("declarator") + if name_part: + if name_part.type == "identifier" or name_part.type == "field_identifier": + name_node = name_part + elif name_part.type == "qualified_identifier": + # C++ class method: Class::method + # Get the last part + name_node = name_part + + # Extract parameters + params_node = declarator.child_by_field_name("parameters") + if params_node: + params = self._extract_cpp_params(params_node, source) + + elif declarator.type == "identifier": + name_node = declarator + # Look for parameters in parent function_definition + params_node = function_node.child_by_field_name("declarator") + if params_node and params_node.type == "function_declarator": + params_list = params_node.child_by_field_name("parameters") + if params_list: + params = self._extract_cpp_params(params_list, source) + + elif declarator.type == "field_identifier": + name_node = declarator + + if not name_node: + return None + + name = self._get_node_text(name_node, source) + + # Try to extract return type from the function_definition + type_node = function_node.child_by_field_name("type") + if type_node: + return_type = self._get_node_text(type_node, source).strip() + else: + # For constructors/destructors, return type is None + pass + + return FunctionInfo( + name=name, + file=str(file_path), + line_start=function_node.start_point[0] + 1, + line_end=function_node.end_point[0] + 1, + parameters=params, + return_type=return_type if return_type else None, + is_method="::" in name or self._is_in_class_context(function_node), + ) + + def _extract_cpp_params(self, params_node: Any, source: bytes) -> list[str]: + """Extract C/C++ function parameters.""" + params: list[str] = [] + for child in params_node.children: + if child.type in ("parameter_declaration", "parameter_list"): + param_text = source[child.start_byte:child.end_byte].decode().strip() + # Clean up the parameter text + param_text = param_text.strip("()") + if param_text and param_text != "void": + params.append(param_text) + return params + + def _is_in_class_context(self, node: Any) -> bool: + """Check if function is inside a class/struct context.""" + current = node + while current: + if current.type in ("class_specifier", "struct_specifier"): + return True + current = current.parent + return False diff --git a/src/codespy/tools/parsers/treesitter/extractors/csharp.py b/src/codespy/tools/parsers/treesitter/extractors/csharp.py new file mode 100644 index 0000000..08fce40 --- /dev/null +++ b/src/codespy/tools/parsers/treesitter/extractors/csharp.py @@ -0,0 +1,140 @@ +"""C# function/method extractor.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +from codespy.tools.parsers.treesitter.base_extractor import BaseExtractor +from codespy.tools.parsers.treesitter.models import FunctionInfo + + +class CSharpExtractor(BaseExtractor): + """Extract method definitions from C# source code.""" + + def extract_functions( + self, + node: Any, + file_path: Path, + source: bytes, + ) -> list[FunctionInfo]: + """Extract C# method definitions.""" + functions: list[FunctionInfo] = [] + + def visit(n: Any, in_type: bool = False) -> None: + if n.type == "method_declaration": + func_info = self._extract_method_info(n, file_path, source) + if func_info: + functions.append(func_info) + + elif n.type == "constructor_declaration": + func_info = self._extract_constructor_info(n, file_path, source) + if func_info: + functions.append(func_info) + + elif n.type == "local_function_statement": + func_info = self._extract_local_function_info(n, file_path, source) + if func_info: + functions.append(func_info) + + for child in n.children: + # Track if we're inside a class/interface/struct + is_type = n.type in ("class_declaration", "interface_declaration", "struct_declaration", "record_declaration") + visit(child, in_type or is_type) + + visit(node) + return functions + + def _extract_method_info( + self, + method_node: Any, + file_path: Path, + source: bytes, + ) -> FunctionInfo | None: + """Extract method info from a method_declaration node.""" + name_node = method_node.child_by_field_name("name") + if not name_node: + return None + + name = self._get_node_text(name_node, source) + params = self._extract_csharp_params(method_node, source) + return_type = self._extract_csharp_return_type(method_node, source) + + return FunctionInfo( + name=name, + file=str(file_path), + line_start=method_node.start_point[0] + 1, + line_end=method_node.end_point[0] + 1, + parameters=params, + return_type=return_type, + is_method=True, + ) + + def _extract_constructor_info( + self, + ctor_node: Any, + file_path: Path, + source: bytes, + ) -> FunctionInfo | None: + """Extract constructor info.""" + name_node = ctor_node.child_by_field_name("name") + if not name_node: + return None + + name = self._get_node_text(name_node, source) + params = self._extract_csharp_params(ctor_node, source) + + return FunctionInfo( + name=name, + file=str(file_path), + line_start=ctor_node.start_point[0] + 1, + line_end=ctor_node.end_point[0] + 1, + parameters=params, + return_type=None, # Constructors return the class type + is_method=True, + ) + + def _extract_local_function_info( + self, + func_node: Any, + file_path: Path, + source: bytes, + ) -> FunctionInfo | None: + """Extract local function info (C# 7.0+).""" + name_node = func_node.child_by_field_name("name") + if not name_node: + return None + + name = self._get_node_text(name_node, source) + params = self._extract_csharp_params(func_node, source) + return_type = self._extract_csharp_return_type(func_node, source) + + return FunctionInfo( + name=name, + file=str(file_path), + line_start=func_node.start_point[0] + 1, + line_end=func_node.end_point[0] + 1, + parameters=params, + return_type=return_type, + is_method=False, # Local functions are not methods + ) + + def _extract_csharp_params(self, node: Any, source: bytes) -> list[str]: + """Extract C# method parameters.""" + params: list[str] = [] + params_node = node.child_by_field_name("parameters") + if params_node: + for child in params_node.children: + if child.type == "parameter": + # Extract parameter info + param_text = self._get_node_text(child, source).strip() + if param_text and param_text not in ("(", ")", ","): + params.append(param_text) + return params + + def _extract_csharp_return_type(self, node: Any, source: bytes) -> str | None: + """Extract C# method return type.""" + type_node = node.child_by_field_name("type") + if type_node: + return self._get_node_text(type_node, source).strip() + return None diff --git a/src/codespy/tools/parsers/treesitter/extractors/go.py b/src/codespy/tools/parsers/treesitter/extractors/go.py index c21402d..462d8df 100644 --- a/src/codespy/tools/parsers/treesitter/extractors/go.py +++ b/src/codespy/tools/parsers/treesitter/extractors/go.py @@ -83,4 +83,4 @@ def _extract_go_return_type(self, node: Any, source: bytes) -> str | None: result = node.child_by_field_name("result") if result: return self._get_node_text(result, source).strip() - return None \ No newline at end of file + return None diff --git a/src/codespy/tools/parsers/treesitter/extractors/java.py b/src/codespy/tools/parsers/treesitter/extractors/java.py index f32feff..45fd79a 100644 --- a/src/codespy/tools/parsers/treesitter/extractors/java.py +++ b/src/codespy/tools/parsers/treesitter/extractors/java.py @@ -70,4 +70,4 @@ def _extract_java_return_type(self, node: Any, source: bytes) -> str | None: type_node = node.child_by_field_name("type") if type_node: return self._get_node_text(type_node, source).strip() - return None \ No newline at end of file + return None diff --git a/src/codespy/tools/parsers/treesitter/extractors/javascript.py b/src/codespy/tools/parsers/treesitter/extractors/javascript.py index 0069a0c..ade578a 100644 --- a/src/codespy/tools/parsers/treesitter/extractors/javascript.py +++ b/src/codespy/tools/parsers/treesitter/extractors/javascript.py @@ -92,4 +92,4 @@ def _extract_js_params(self, node: Any, source: bytes) -> list[str]: if child.type in ("identifier", "required_parameter", "optional_parameter"): param_text = self._get_node_text(child, source) params.append(param_text.strip()) - return params \ No newline at end of file + return params diff --git a/src/codespy/tools/parsers/treesitter/extractors/kotlin.py b/src/codespy/tools/parsers/treesitter/extractors/kotlin.py index 006689c..db2ac7e 100644 --- a/src/codespy/tools/parsers/treesitter/extractors/kotlin.py +++ b/src/codespy/tools/parsers/treesitter/extractors/kotlin.py @@ -46,4 +46,4 @@ def visit(n: Any, in_class: bool = False) -> None: visit(child, in_class) visit(node) - return functions \ No newline at end of file + return functions diff --git a/src/codespy/tools/parsers/treesitter/extractors/objc.py b/src/codespy/tools/parsers/treesitter/extractors/objc.py index 47f2c35..339e615 100644 --- a/src/codespy/tools/parsers/treesitter/extractors/objc.py +++ b/src/codespy/tools/parsers/treesitter/extractors/objc.py @@ -78,4 +78,4 @@ def _extract_c_declarator_name(self, node: Any, source: bytes) -> str | None: result = self._extract_c_declarator_name(child, source) if result: return result - return None \ No newline at end of file + return None diff --git a/src/codespy/tools/parsers/treesitter/extractors/php.py b/src/codespy/tools/parsers/treesitter/extractors/php.py new file mode 100644 index 0000000..784187c --- /dev/null +++ b/src/codespy/tools/parsers/treesitter/extractors/php.py @@ -0,0 +1,170 @@ +"""PHP function extractor.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +from codespy.tools.parsers.treesitter.base_extractor import BaseExtractor +from codespy.tools.parsers.treesitter.models import FunctionInfo + + +class PHPExtractor(BaseExtractor): + """Extract function definitions from PHP source code.""" + + def extract_functions( + self, + node: Any, + file_path: Path, + source: bytes, + ) -> list[FunctionInfo]: + """Extract PHP function definitions.""" + functions: list[FunctionInfo] = [] + + def visit(n: Any, in_class: bool = False) -> None: + if n.type == "function_definition": + func_info = self._extract_function_info(n, file_path, source) + if func_info: + functions.append(func_info) + + elif n.type == "method_declaration": + func_info = self._extract_method_info(n, file_path, source) + if func_info: + functions.append(func_info) + + elif n.type == "anonymous_function": + func_info = self._extract_anonymous_function_info(n, file_path, source) + if func_info: + functions.append(func_info) + + elif n.type == "arrow_function": + func_info = self._extract_arrow_function_info(n, file_path, source) + if func_info: + functions.append(func_info) + + for child in n.children: + # Track if we're inside a class + is_class = n.type in ("class_declaration", "interface_declaration", "trait_declaration") + visit(child, in_class or is_class) + + visit(node) + return functions + + def _extract_function_info( + self, + func_node: Any, + file_path: Path, + source: bytes, + ) -> FunctionInfo | None: + """Extract function info from a function_definition node.""" + name_node = func_node.child_by_field_name("name") + if not name_node: + return None + + name = self._get_node_text(name_node, source) + params = self._extract_php_params(func_node, source) + return_type = self._extract_php_return_type(func_node, source) + + return FunctionInfo( + name=name, + file=str(file_path), + line_start=func_node.start_point[0] + 1, + line_end=func_node.end_point[0] + 1, + parameters=params, + return_type=return_type, + is_method=False, # Functions are not methods + ) + + def _extract_method_info( + self, + method_node: Any, + file_path: Path, + source: bytes, + ) -> FunctionInfo | None: + """Extract method info from a method_declaration node.""" + name_node = method_node.child_by_field_name("name") + if not name_node: + return None + + name = self._get_node_text(name_node, source) + params = self._extract_php_params(method_node, source) + return_type = self._extract_php_return_type(method_node, source) + + return FunctionInfo( + name=name, + file=str(file_path), + line_start=method_node.start_point[0] + 1, + line_end=method_node.end_point[0] + 1, + parameters=params, + return_type=return_type, + is_method=True, + ) + + def _extract_anonymous_function_info( + self, + func_node: Any, + file_path: Path, + source: bytes, + ) -> FunctionInfo | None: + """Extract anonymous function (closure) info.""" + # Anonymous functions don't have a name, use placeholder + params = self._extract_php_params(func_node, source) + return_type = self._extract_php_return_type(func_node, source) + + return FunctionInfo( + name="(anonymous)", + file=str(file_path), + line_start=func_node.start_point[0] + 1, + line_end=func_node.end_point[0] + 1, + parameters=params, + return_type=return_type, + is_method=False, + ) + + def _extract_arrow_function_info( + self, + func_node: Any, + file_path: Path, + source: bytes, + ) -> FunctionInfo | None: + """Extract arrow function (fn() => expr) info.""" + params = self._extract_php_params(func_node, source) + return_type = self._extract_php_return_type(func_node, source) + + return FunctionInfo( + name="(arrow)", + file=str(file_path), + line_start=func_node.start_point[0] + 1, + line_end=func_node.end_point[0] + 1, + parameters=params, + return_type=return_type, + is_method=False, + ) + + def _extract_php_params(self, node: Any, source: bytes) -> list[str]: + """Extract PHP function parameters.""" + params: list[str] = [] + params_node = node.child_by_field_name("parameters") + if not params_node: + return params + + for child in params_node.children: + if child.type == "parameter": + # Extract parameter info + param_text = self._get_node_text(child, source).strip() + if param_text and param_text not in ("(", ")", ","): + # Extract just the variable name if possible + var_node = child.child_by_field_name("name") + if var_node: + params.append(self._get_node_text(var_node, source)) + else: + params.append(param_text) + + return params + + def _extract_php_return_type(self, node: Any, source: bytes) -> str | None: + """Extract PHP function return type.""" + type_node = node.child_by_field_name("return_type") + if type_node: + return self._get_node_text(type_node, source).strip() + return None diff --git a/src/codespy/tools/parsers/treesitter/extractors/python.py b/src/codespy/tools/parsers/treesitter/extractors/python.py index 96769bd..ef82a6c 100644 --- a/src/codespy/tools/parsers/treesitter/extractors/python.py +++ b/src/codespy/tools/parsers/treesitter/extractors/python.py @@ -66,4 +66,4 @@ def _extract_python_return_type(self, node: Any, source: bytes) -> str | None: return_type = node.child_by_field_name("return_type") if return_type: return self._get_node_text(return_type, source).strip() - return None \ No newline at end of file + return None diff --git a/src/codespy/tools/parsers/treesitter/extractors/ripgrep_fallback.py b/src/codespy/tools/parsers/treesitter/extractors/ripgrep_fallback.py new file mode 100644 index 0000000..5d2d6c6 --- /dev/null +++ b/src/codespy/tools/parsers/treesitter/extractors/ripgrep_fallback.py @@ -0,0 +1,415 @@ +"""Ripgrep + heuristics fallback extractor for function detection. + +This module provides a generic fallback for extracting function information +when tree-sitter is not available. It uses ripgrep to find function definition +lines and interval intersection to determine which functions are affected by +changed lines. + +Algorithm: + 1. Run ripgrep on the file with generic definition patterns + → sorted list of (line_number, function_name) + + 2. Derive implicit boundaries: each function spans from its definition + line to the line before the next definition (or EOF) + + 3. Intersect these boundaries with changed_line_ranges + → return functions that contain at least one changed line + +This approach handles all cases elegantly: + - Body changes: Changed lines fall within a function's derived range + - New function: The definition line IS a changed line, matches itself + - Signature changes: Definition line is changed, matches itself + - Multiple functions: Multiple intersections found +""" + +from __future__ import annotations + +import logging +import re +import shutil +import subprocess +from pathlib import Path + +from codespy.tools.parsers.treesitter.models import FunctionInfo + +logger = logging.getLogger(__name__) + + +class RipgrepHeuristicsExtractor: + """Generic fallback extractor using ripgrep + interval intersection. + + Instead of parsing the entire file with complex AST analysis, this extractor: + 1. Uses ripgrep to find all function definition lines in the file + 2. Derives implicit boundaries: each function spans to the next definition + 3. Intersects these ranges with the changed line ranges from the patch + + This is much faster than AST parsing and handles all edge cases: + - Body changes: Lines fall within derived range + - New functions: Definition line IS a changed line + - Modified signatures: Definition line is changed + """ + + # Generic patterns that match function definitions across languages. + # Ordered by specificity - more specific patterns first. + DEFINITION_PATTERNS: list[tuple[str, re.Pattern]] = [ + # Keyword-based: def, func, fn, fun, function, sub, proc + # Matches: Python, Ruby, Go, JavaScript, TypeScript, Rust, etc. + ( + "keyword", + re.compile( + r"^[\s]*(?:" # leading whitespace + r"(?:pub|priv|protected|private|public|export|async|static|inline|const|final)\s+)*" # modifiers + r"(?:def|func|fn|fun|function|sub|proc)\s+" # keyword + r"(?:self\.)?" # optional self. for Ruby + r"(\w+)" # function name (capture group 1) + ), + ), + # C-style: return_type name(params) { + # Matches: C, C++, C#, Java, PHP, etc. + ( + "c_style", + re.compile( + r"^[\s]*" # leading whitespace + r"(?:[\w\*&<>,:\s]+\s+)" # return type with modifiers/pointers + r"(\w+)" # function name (capture group 1) + r"\s*\([^;]*$" # opening paren, avoid forward declarations ending in ; + ), + ), + # Shell/Bash: function name() { or name() { + ( + "shell", + re.compile( + r"^[\s]*" # leading whitespace + r"(?:function\s+)?" # optional 'function' keyword + r"(\w+)" # function name + r"\s*\(\s*\)" # empty parentheses + ), + ), + # SQL: CREATE FUNCTION/PROCEDURE/TRIGGER name + ( + "sql", + re.compile( + r"(?i)" # case-insensitive inline flag + r"^[\s]*" # leading whitespace + r"(?:CREATE\s+(?:OR\s+REPLACE\s+)?)?" # optional CREATE OR REPLACE + r"(?:PROCEDURE|FUNCTION|TRIGGER)\s+" # object type + r"(?:[\w.]+\s+)?" # optional schema prefix + r"(\w+)" # name + ), + ), + ] + + # Map file extensions to preferred pattern order + EXTENSION_PRIORITY: dict[str, list[str]] = { + ".py": ["keyword"], # Python uses 'def' + ".rb": ["keyword"], # Ruby uses 'def' + ".go": ["keyword"], # Go uses 'func' + ".rs": ["keyword"], # Rust uses 'fn' + ".js": ["keyword", "c_style"], # JavaScript can use both + ".ts": ["keyword", "c_style"], # TypeScript + ".c": ["c_style"], # C uses c_style + ".cpp": ["c_style"], # C++ + ".cc": ["c_style"], + ".cxx": ["c_style"], + ".h": ["c_style"], + ".hpp": ["c_style"], + ".cs": ["c_style"], # C# + ".java": ["c_style"], # Java + ".php": ["c_style", "keyword"], # PHP uses both + ".sh": ["shell", "keyword"], # Shell + ".bash": ["shell", "keyword"], + ".zsh": ["shell", "keyword"], + ".sql": ["sql"], # SQL + } + + def __init__(self, repo_path: Path) -> None: + """Initialize the extractor. + + Args: + repo_path: Path to the repository root (for context) + """ + self.repo_path = repo_path + self._rg_available = shutil.which("rg") is not None + + def _get_patterns_for_file(self, file_path: Path) -> list[tuple[str, re.Pattern]]: + """Get definition patterns ordered by priority for the file extension.""" + ext = file_path.suffix.lower() + priority_order = self.EXTENSION_PRIORITY.get(ext, []) + + # Build ordered list based on priority + ordered = [] + for pattern_name in priority_order: + for name, pattern in self.DEFINITION_PATTERNS: + if name == pattern_name and (name, pattern) not in ordered: + ordered.append((name, pattern)) + + # Add remaining patterns not in priority list + for name, pattern in self.DEFINITION_PATTERNS: + if (name, pattern) not in ordered: + ordered.append((name, pattern)) + + return ordered + + def _find_definitions( + self, + file_path: Path, + ) -> list[tuple[int, str, str]]: + """Find all function definition lines in a file using ripgrep. + + Args: + file_path: Path to the source file + + Returns: + List of (line_number, function_name, full_line) tuples, sorted by line_number + """ + if not self._rg_available: + logger.debug("ripgrep not available, skipping definition search") + return [] + + patterns = self._get_patterns_for_file(file_path) + if not patterns: + return [] + + # Combine patterns with alternation for single ripgrep call + # Extract just the pattern regexes + pattern_regexes = [p[1].pattern for p in patterns] + combined_pattern = "|".join(f"({p})" for p in pattern_regexes) + + definitions: list[tuple[int, str, str]] = [] + + try: + cmd = [ + "rg", + "--line-number", + "--no-heading", + "--with-filename", + "--color=never", + "--multiline", # Handle multi-line patterns + combined_pattern, + str(file_path), + ] + + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=30, + cwd=str(self.repo_path), + ) + + # ripgrep returns 1 when no matches found (not an error) + if result.returncode > 1: + logger.debug(f"ripgrep search failed: {result.stderr}") + return [] + + # Parse results: file:line:content + for line in result.stdout.strip().split("\n"): + if not line: + continue + + # Parse: filepath:line:content + # Handle Windows/Unix path differences + parts = line.split(":", 2) + if len(parts) < 3: + continue + + try: + line_num = int(parts[1]) + except ValueError: + continue + + content = parts[2] + + # Try each pattern to extract function name + for _pattern_name, pattern in patterns: + match = pattern.match(content) + if match: + func_name = match.group(1) if match.lastindex else None + if func_name: + definitions.append((line_num, func_name, content.strip())) + break # Found a match, move to next line + + except subprocess.TimeoutExpired: + logger.warning(f"ripgrep search timed out for {file_path}") + except Exception as e: + logger.debug(f"ripgrep search failed for {file_path}: {e}") + + # Sort by line number and remove duplicates + seen = set() + unique_defs = [] + for line_num, func_name, full_line in sorted(definitions): + key = (line_num, func_name) + if key not in seen: + seen.add(key) + unique_defs.append((line_num, func_name, full_line)) + + return unique_defs + + def _derive_boundaries( + self, + definitions: list[tuple[int, str, str]], + total_lines: int, + ) -> list[tuple[int, int, str, str]]: + """Derive function boundaries from definition lines. + + Each function spans from its definition line to the line before + the next function's definition (or EOF). + + Args: + definitions: List of (line_number, function_name, full_line) + total_lines: Total number of lines in the file + + Returns: + List of (start_line, end_line, function_name, signature_line) + """ + boundaries = [] + for i, (line_num, func_name, full_line) in enumerate(definitions): + # End is line before next definition, or EOF + end_line = definitions[i + 1][0] - 1 if i + 1 < len(definitions) else total_lines + + boundaries.append((line_num, end_line, func_name, full_line)) + + return boundaries + + def _count_file_lines(self, file_path: Path) -> int: + """Count total lines in a file efficiently.""" + try: + # Use wc -l for efficiency on large files + result = subprocess.run( + ["wc", "-l", str(file_path)], + capture_output=True, + text=True, + timeout=10, + ) + if result.returncode == 0: + # Parse: " 123 filename" + parts = result.stdout.strip().split() + if parts: + return int(parts[0]) + except Exception: + pass + + # Fallback: read and count + try: + with open(file_path, "rb") as f: + return sum(1 for _ in f) + except Exception as e: + logger.debug(f"Failed to count lines in {file_path}: {e}") + return 0 + + def _extract_parameters(self, signature_line: str) -> list[str]: + """Extract parameter names from a function signature line. + + Best-effort extraction for common patterns. + """ + params: list[str] = [] + + # Look for parentheses with content + match = re.search(r"\(([^)]*)\)", signature_line) + if not match: + return params + + params_str = match.group(1).strip() + if not params_str: + return params + + # Split by comma, handle simple cases + for param in params_str.split(","): + param = param.strip() + if not param: + continue + + # Extract parameter name (last word before = or end) + # Examples: "int x", "const string& name", "x int" + param = param.split("=")[0].strip() # Remove default values + words = param.split() + if words: + # Last word is usually the parameter name + name = words[-1].rstrip("*&") + if re.match(r"^[a-zA-Z_]\w*$", name): + params.append(name) + + return params + + def extract_functions( + self, + file_path: Path, + changed_line_ranges: list[tuple[int, int]], + ) -> list[FunctionInfo]: + """Extract functions affected by the changed line ranges. + + Args: + file_path: Path to the source file + changed_line_ranges: List of (start, end) tuples for changed lines + + Returns: + List of FunctionInfo for functions that overlap with changed lines + """ + if not file_path.exists(): + logger.debug(f"File not found: {file_path}") + return [] + + if not changed_line_ranges: + logger.debug("No changed line ranges provided") + return [] + + # Step 1: Find all function definitions + definitions = self._find_definitions(file_path) + if not definitions: + logger.debug(f"No function definitions found in {file_path}") + return [] + + # Step 2: Get total lines and derive boundaries + total_lines = self._count_file_lines(file_path) + boundaries = self._derive_boundaries(definitions, total_lines) + + # Step 3: Intersect with changed ranges + affected_functions: list[FunctionInfo] = [] + seen_names: set[str] = set() + + for func_start, func_end, func_name, signature_line in boundaries: + # Check if this function overlaps with any changed range + overlaps = False + for change_start, change_end in changed_line_ranges: + # Overlap condition: func_start <= change_end AND func_end >= change_start + if func_start <= change_end and func_end >= change_start: + overlaps = True + break + + if overlaps and func_name not in seen_names: + params = self._extract_parameters(signature_line) + + # Build return_type from signature (best effort) + return_type = None + # Try to extract return type from C-style signatures + c_match = re.match( + r"^[\s]*([\w\*&<>,:\s]+)\s+\w+\s*\(", signature_line + ) + if c_match: + potential = c_match.group(1).strip() + # Filter out modifiers + modifiers = {"static", "inline", "extern", "virtual", "const", "async", "public", "private", "protected"} + words = potential.split() + filtered = [w for w in words if w not in modifiers] + if filtered: + return_type = " ".join(filtered) + + affected_functions.append( + FunctionInfo( + name=func_name, + file=str(file_path), + line_start=func_start, + line_end=func_end, + parameters=params, + return_type=return_type, + is_method=False, # Cannot determine from single line + receiver_type=None, + docstring=None, + ) + ) + seen_names.add(func_name) + + logger.debug( + f"Found {len(affected_functions)} affected functions in {file_path}" + ) + return affected_functions diff --git a/src/codespy/tools/parsers/treesitter/extractors/ruby.py b/src/codespy/tools/parsers/treesitter/extractors/ruby.py new file mode 100644 index 0000000..a7e1d80 --- /dev/null +++ b/src/codespy/tools/parsers/treesitter/extractors/ruby.py @@ -0,0 +1,132 @@ +"""Ruby function/method extractor.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +from codespy.tools.parsers.treesitter.base_extractor import BaseExtractor +from codespy.tools.parsers.treesitter.models import FunctionInfo + + +class RubyExtractor(BaseExtractor): + """Extract method definitions from Ruby source code.""" + + def extract_functions( + self, + node: Any, + file_path: Path, + source: bytes, + ) -> list[FunctionInfo]: + """Extract Ruby method definitions.""" + functions: list[FunctionInfo] = [] + + def visit(n: Any, in_class: bool = False) -> None: + if n.type == "method": + func_info = self._extract_method_info(n, file_path, source, in_class) + if func_info: + functions.append(func_info) + + elif n.type == "singleton_method": + func_info = self._extract_singleton_method_info(n, file_path, source) + if func_info: + functions.append(func_info) + + elif n.type == "lambda": + # Anonymous lambda - skip (no name) + pass + + elif n.type == "block": + # Block passed to a method - skip + pass + + for child in n.children: + # Track if we're inside a class/module + is_class = n.type in ("class", "module", "singleton_class") + visit(child, in_class or is_class) + + visit(node) + return functions + + def _extract_method_info( + self, + method_node: Any, + file_path: Path, + source: bytes, + in_class: bool, + ) -> FunctionInfo | None: + """Extract method info from a method node.""" + name_node = method_node.child_by_field_name("name") + if not name_node: + return None + + name = self._get_node_text(name_node, source) + params = self._extract_ruby_params(method_node, source) + + return FunctionInfo( + name=name, + file=str(file_path), + line_start=method_node.start_point[0] + 1, + line_end=method_node.end_point[0] + 1, + parameters=params, + return_type=None, # Ruby is dynamically typed + is_method=in_class, + ) + + def _extract_singleton_method_info( + self, + method_node: Any, + file_path: Path, + source: bytes, + ) -> FunctionInfo | None: + """Extract singleton method info (e.g., def self.method_name).""" + name_node = method_node.child_by_field_name("name") + if not name_node: + return None + + name = "self." + self._get_node_text(name_node, source) + params = self._extract_ruby_params(method_node, source) + + return FunctionInfo( + name=name, + file=str(file_path), + line_start=method_node.start_point[0] + 1, + line_end=method_node.end_point[0] + 1, + parameters=params, + return_type=None, + is_method=True, + ) + + def _extract_ruby_params(self, node: Any, source: bytes) -> list[str]: + """Extract Ruby method parameters.""" + params: list[str] = [] + params_node = node.child_by_field_name("parameters") + if not params_node: + return params + + for child in params_node.children: + # Ruby parameters can be: identifier, optional_parameter, keyword_parameter, etc. + if child.type in ("identifier", "simple_parameter"): + param_name = self._get_node_text(child, source) + if param_name and param_name not in ("(", ")", ",", "|", "&"): + params.append(param_name) + elif child.type == "optional_parameter": + name_node = child.child_by_field_name("name") + if name_node: + param_name = self._get_node_text(name_node, source) + if param_name: + params.append(param_name) + elif child.type == "block_parameter": + # &block parameter + name_node = child.child_by_field_name("name") + if name_node: + param_name = "&" + self._get_node_text(name_node, source) + params.append(param_name) + elif child.type == "keyword_parameter": + name_node = child.child_by_field_name("name") + if name_node: + param_name = self._get_node_text(name_node, source) + if param_name: + params.append(f"{param_name}:") + + return params diff --git a/src/codespy/tools/parsers/treesitter/extractors/rust.py b/src/codespy/tools/parsers/treesitter/extractors/rust.py index 44676c7..095cd85 100644 --- a/src/codespy/tools/parsers/treesitter/extractors/rust.py +++ b/src/codespy/tools/parsers/treesitter/extractors/rust.py @@ -56,10 +56,7 @@ def _extract_rust_params(self, node: Any, source: bytes) -> list[str]: params_node = node.child_by_field_name("parameters") if params_node: for child in params_node.children: - if child.type == "parameter": - param_text = self._get_node_text(child, source) - params.append(param_text.strip()) - elif child.type == "self_parameter": + if child.type == "parameter" or child.type == "self_parameter": param_text = self._get_node_text(child, source) params.append(param_text.strip()) return params @@ -69,4 +66,4 @@ def _extract_rust_return_type(self, node: Any, source: bytes) -> str | None: return_type = node.child_by_field_name("return_type") if return_type: return self._get_node_text(return_type, source).strip() - return None \ No newline at end of file + return None diff --git a/src/codespy/tools/parsers/treesitter/extractors/swift.py b/src/codespy/tools/parsers/treesitter/extractors/swift.py index a66b909..0818279 100644 --- a/src/codespy/tools/parsers/treesitter/extractors/swift.py +++ b/src/codespy/tools/parsers/treesitter/extractors/swift.py @@ -46,4 +46,4 @@ def visit(n: Any, in_class: bool = False) -> None: visit(child, in_class) visit(node) - return functions \ No newline at end of file + return functions diff --git a/src/codespy/tools/parsers/treesitter/parser.py b/src/codespy/tools/parsers/treesitter/parser.py index 21695c8..894d44d 100644 --- a/src/codespy/tools/parsers/treesitter/parser.py +++ b/src/codespy/tools/parsers/treesitter/parser.py @@ -8,12 +8,17 @@ from codespy.tools.parsers.treesitter.base_extractor import BaseExtractor from codespy.tools.parsers.treesitter.extractors import ( + BashExtractor, + CppExtractor, + CSharpExtractor, GoExtractor, JavaExtractor, JavaScriptExtractor, KotlinExtractor, ObjCExtractor, + PHPExtractor, PythonExtractor, + RubyExtractor, RustExtractor, SwiftExtractor, TerraformExtractor, @@ -41,6 +46,28 @@ from tree_sitter import Language, Node, Parser TREE_SITTER_AVAILABLE = True + + # Try to import new language grammars (optional) + try: + import tree_sitter_cpp as ts_cpp + except ImportError: + ts_cpp = None # type: ignore + try: + import tree_sitter_c_sharp as ts_csharp + except ImportError: + ts_csharp = None # type: ignore + try: + import tree_sitter_ruby as ts_ruby + except ImportError: + ts_ruby = None # type: ignore + try: + import tree_sitter_php as ts_php + except ImportError: + ts_php = None # type: ignore + try: + import tree_sitter_bash as ts_bash + except ImportError: + ts_bash = None # type: ignore except ImportError: TREE_SITTER_AVAILABLE = False Parser = Any # type: ignore[misc] @@ -75,6 +102,21 @@ class TreeSitterParser: "rs": ("rust", ["function_item"]), "tf": ("hcl", ["block"]), "tfvars": ("hcl", ["block"]), + # New languages + "c": ("cpp", ["function_definition"]), + "cpp": ("cpp", ["function_definition"]), + "cc": ("cpp", ["function_definition"]), + "cxx": ("cpp", ["function_definition"]), + "h": ("cpp", ["function_definition"]), + "hpp": ("cpp", ["function_definition"]), + "hh": ("cpp", ["function_definition"]), + "hxx": ("cpp", ["function_definition"]), + "cs": ("csharp", ["method_declaration", "constructor_declaration"]), + "rb": ("ruby", ["method", "singleton_method"]), + "php": ("php", ["function_definition", "method_declaration"]), + "sh": ("bash", ["function_definition"]), + "bash": ("bash", ["function_definition"]), + "zsh": ("bash", ["function_definition"]), } def __init__(self, repo_path: Path) -> None: @@ -111,6 +153,12 @@ def _init_extractors(self) -> None: "objc": ObjCExtractor(), "rust": RustExtractor(), "hcl": TerraformExtractor(), + # New languages + "cpp": CppExtractor(), + "csharp": CSharpExtractor(), + "ruby": RubyExtractor(), + "php": PHPExtractor(), + "bash": BashExtractor(), } def _init_languages(self) -> None: @@ -130,11 +178,20 @@ def _init_languages(self) -> None: ("objc", lambda: ts_objc.language()), ("rust", lambda: ts_rust.language()), ("hcl", lambda: ts_hcl.language()), + # New languages (conditional) + ("cpp", lambda: ts_cpp.language() if ts_cpp else None), + ("csharp", lambda: ts_csharp.language() if ts_csharp else None), + ("ruby", lambda: ts_ruby.language() if ts_ruby else None), + ("php", lambda: ts_php.language() if ts_php else None), + ("bash", lambda: ts_bash.language() if ts_bash else None), ] for lang_name, lang_func in language_configs: try: - self._languages[lang_name] = Language(lang_func()) + lang_result = lang_func() + if lang_result is None: + continue # Language grammar not available + self._languages[lang_name] = Language(lang_result) parser = Parser(self._languages[lang_name]) self._parsers[lang_name] = parser except Exception as e: diff --git a/src/codespy/tools/parsers/treesitter/server.py b/src/codespy/tools/parsers/treesitter/server.py index 0b4c8ee..eeb5617 100644 --- a/src/codespy/tools/parsers/treesitter/server.py +++ b/src/codespy/tools/parsers/treesitter/server.py @@ -341,7 +341,7 @@ def get_terraform_summary(file_path: str, content: str | None = None) -> dict[st # Suppress noisy MCP server "Processing request" logs logging.getLogger("mcp.server").setLevel(logging.WARNING) logging.getLogger("mcp.server.lowlevel").setLevel(logging.WARNING) - + repo_path = sys.argv[1] if len(sys.argv) > 1 else "." _parser = TreeSitterParser(Path(repo_path)) mcp.run() diff --git a/src/codespy/tools/storage/__init__.py b/src/codespy/tools/storage/__init__.py new file mode 100644 index 0000000..d39c1e0 --- /dev/null +++ b/src/codespy/tools/storage/__init__.py @@ -0,0 +1,27 @@ +"""Unified storage — local filesystem and S3 backends with a shared interface.""" + +from codespy.tools.storage.base import Storage +from codespy.tools.storage.filesystem.client import FileSystem +from codespy.tools.storage.models import ( + Content, + Entry, + EntryType, + Info, + Listing, + OperationResult, + TreeNode, +) +from codespy.tools.storage.s3.client import S3Client + +__all__ = [ + "Content", + "Entry", + "EntryType", + "FileSystem", + "Info", + "Listing", + "OperationResult", + "S3Client", + "Storage", + "TreeNode", +] diff --git a/src/codespy/tools/storage/base.py b/src/codespy/tools/storage/base.py new file mode 100644 index 0000000..f3f5f4d --- /dev/null +++ b/src/codespy/tools/storage/base.py @@ -0,0 +1,173 @@ +"""Abstract base class defining the unified storage interface.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod + +from codespy.tools.storage.models import ( + Content, + Info, + Listing, + OperationResult, + TreeNode, +) + + +class Storage(ABC): + """Abstract storage backend — implemented by both FileSystem and S3Client. + + Provides a uniform interface for reading, writing, and navigating file-like + storage, whether backed by the local filesystem or an S3 bucket. + """ + + # ------------------------------------------------------------------ + # Read operations + # ------------------------------------------------------------------ + + @abstractmethod + def exists(self, path: str = "") -> bool: + """Check whether a path exists. + + Args: + path: Relative path to check. + + Returns: + True if the path exists. + """ + ... + + @abstractmethod + def verify_access(self) -> None: + """Verify the storage backend is accessible. + + Raises: + FileNotFoundError: If the storage root does not exist. + NotADirectoryError: If the storage root is not a directory. + PermissionError: If the storage root is not readable. + Exception: Backend-specific errors (e.g., S3 credentials, bucket access). + """ + ... + + @abstractmethod + def get_info(self, path: str = "") -> Info: + """Get metadata about a file or directory. + + Args: + path: Relative path. + + Returns: + Info with metadata. + + Raises: + FileNotFoundError: If the path does not exist. + """ + ... + + @abstractmethod + def list_directory( + self, + path: str = "", + include_hidden: bool = False, + ) -> Listing: + """List the contents of a directory (one level deep). + + Args: + path: Relative directory path. + include_hidden: Whether to include hidden entries. + + Returns: + Listing with entries. + """ + ... + + @abstractmethod + def read_file( + self, + path: str, + max_bytes: int = 100_000, + max_lines: int | None = None, + ) -> Content: + """Read a file as text. + + Args: + path: Relative file path. + max_bytes: Maximum bytes to read. + max_lines: Maximum lines to read. + + Returns: + Content with file data (error field set on failure). + """ + ... + + @abstractmethod + def get_tree( + self, + path: str = "", + max_depth: int = 3, + include_hidden: bool = False, + ) -> TreeNode: + """Get a tree representation of a directory. + + Args: + path: Relative directory path. + max_depth: Maximum recursion depth. + include_hidden: Whether to include hidden entries. + + Returns: + TreeNode representing the directory tree. + """ + ... + + def get_tree_string( + self, + path: str = "", + max_depth: int = 3, + include_hidden: bool = False, + ) -> str: + """Get a string representation of the directory tree. + + Args: + path: Relative directory path. + max_depth: Maximum recursion depth. + include_hidden: Whether to include hidden entries. + + Returns: + String representation of the tree. + """ + tree = self.get_tree(path, max_depth, include_hidden) + return tree.to_string() + + # ------------------------------------------------------------------ + # Write operations + # ------------------------------------------------------------------ + + @abstractmethod + def write_file( + self, + path: str, + content: str, + content_type: str = "text/plain", + ) -> OperationResult: + """Write text content to a file. + + Args: + path: Relative file path. + content: Text content to write (UTF-8 encoded). + content_type: MIME type hint (used by S3; ignored by filesystem). + + Returns: + OperationResult indicating success or failure. + """ + ... + + @abstractmethod + def delete_file(self, path: str) -> OperationResult: + """Delete a file. + + Args: + path: Relative file path to delete. + + Returns: + OperationResult indicating success or failure. + """ + ... diff --git a/src/codespy/tools/storage/filesystem/__init__.py b/src/codespy/tools/storage/filesystem/__init__.py new file mode 100644 index 0000000..17aec6c --- /dev/null +++ b/src/codespy/tools/storage/filesystem/__init__.py @@ -0,0 +1,5 @@ +"""Local filesystem storage backend.""" + +from codespy.tools.storage.filesystem.client import FileSystem + +__all__ = ["FileSystem"] diff --git a/src/codespy/tools/filesystem/client.py b/src/codespy/tools/storage/filesystem/client.py similarity index 53% rename from src/codespy/tools/filesystem/client.py rename to src/codespy/tools/storage/filesystem/client.py index 1f48a23..e364f5e 100644 --- a/src/codespy/tools/filesystem/client.py +++ b/src/codespy/tools/storage/filesystem/client.py @@ -1,22 +1,28 @@ -"""FileSystem client for file operations.""" +"""FileSystem client for local filesystem operations.""" + +from __future__ import annotations import logging +import os +from datetime import UTC, datetime from pathlib import Path -from codespy.tools.filesystem.models import ( - DirectoryEntry, - DirectoryListing, +from codespy.tools.storage.base import Storage +from codespy.tools.storage.models import ( + Content, + Entry, EntryType, - FileContent, - FileInfo, + Info, + Listing, + OperationResult, TreeNode, ) logger = logging.getLogger(__name__) -class FileSystem: - """Client for filesystem operations. +class FileSystem(Storage): + """Local filesystem storage client. Provides secure file operations restricted to a root directory. """ @@ -41,8 +47,8 @@ def __init__(self, root: str | Path, create_if_missing: bool = True) -> None: """Initialize the filesystem client. Args: - root: Root directory for all operations - create_if_missing: Create the root directory if it doesn't exist + root: Root directory for all operations. + create_if_missing: Create the root directory if it doesn't exist. """ self.root = Path(root).resolve() if not self.root.exists(): @@ -58,20 +64,19 @@ def _resolve_path(self, path: str) -> Path: """Resolve a path relative to root, with security checks. Args: - path: Relative path + path: Relative path. Returns: - Absolute path + Absolute path. Raises: - ValueError: If path escapes root directory + ValueError: If path escapes root directory. """ if not path or path == ".": return self.root resolved = (self.root / path).resolve() - # Security check: ensure path is within root try: resolved.relative_to(self.root) except ValueError: @@ -79,57 +84,46 @@ def _resolve_path(self, path: str) -> Path: return resolved - def exists(self, path: str = "") -> bool: - """Check if a path exists. + # ------------------------------------------------------------------ + # Read operations + # ------------------------------------------------------------------ - Args: - path: Relative path to check - - Returns: - True if path exists - """ + def exists(self, path: str = "") -> bool: + """Check if a path exists.""" try: resolved = self._resolve_path(path) return resolved.exists() except ValueError: return False - def get_info(self, path: str = "") -> FileInfo: - """Get information about a file or directory. - - Args: - path: Relative path - - Returns: - FileInfo with metadata + def verify_access(self) -> None: + """Verify the filesystem root is accessible. Raises: - FileNotFoundError: If path does not exist + FileNotFoundError: If the memory root does not exist. + NotADirectoryError: If the memory root is not a directory. + PermissionError: If the memory root is not readable. """ + if not self.root.exists(): + raise FileNotFoundError(f"Memory root does not exist: {self.root}") + if not self.root.is_dir(): + raise NotADirectoryError(f"Memory root is not a directory: {self.root}") + if not os.access(self.root, os.R_OK): + raise PermissionError(f"Memory root is not readable: {self.root}") + + def get_info(self, path: str = "") -> Info: + """Get information about a file or directory.""" resolved = self._resolve_path(path) if not resolved.exists(): raise FileNotFoundError(f"Path not found: {path}") - - return FileInfo.from_path(resolved, self.root) + return Info.from_path(resolved, self.root) def list_directory( self, path: str = "", include_hidden: bool = False, - ) -> DirectoryListing: - """List contents of a directory. - - Args: - path: Relative path to directory - include_hidden: Whether to include hidden files (starting with .) - - Returns: - DirectoryListing with entries - - Raises: - FileNotFoundError: If path does not exist - NotADirectoryError: If path is not a directory - """ + ) -> Listing: + """List contents of a directory.""" resolved = self._resolve_path(path) if not resolved.exists(): @@ -137,13 +131,12 @@ def list_directory( if not resolved.is_dir(): raise NotADirectoryError(f"Not a directory: {path}") - entries: list[DirectoryEntry] = [] + entries: list[Entry] = [] total_files = 0 total_directories = 0 try: for entry in sorted(resolved.iterdir(), key=lambda x: (x.is_file(), x.name.lower())): - # Skip hidden files unless requested if not include_hidden and entry.name.startswith("."): continue @@ -156,21 +149,17 @@ def list_directory( entry_type = EntryType.FILE total_files += 1 - size = entry.stat().st_size if entry_type == EntryType.FILE else 0 + stat = entry.stat() + size = stat.st_size if entry_type == EntryType.FILE else 0 + modified_at = datetime.fromtimestamp(stat.st_mtime, tz=UTC) if entry_type == EntryType.FILE else None - entries.append( - DirectoryEntry( - name=entry.name, - entry_type=entry_type, - size=size, - ) - ) + entries.append(Entry(name=entry.name, entry_type=entry_type, size=size, modified_at=modified_at)) except PermissionError as e: logger.warning(f"Permission denied listing {path}: {e}") rel_path = str(resolved.relative_to(self.root)) if resolved != self.root else "." - return DirectoryListing( + return Listing( path=rel_path, entries=entries, total_files=total_files, @@ -182,27 +171,14 @@ def read_file( path: str, max_bytes: int = 100_000, max_lines: int | None = None, - ) -> FileContent: - """Read contents of a file. - - Args: - path: Relative path to file - max_bytes: Maximum bytes to read (default 100KB) - max_lines: Maximum lines to read (optional) - - Returns: - FileContent with file data - - Raises: - FileNotFoundError: If file does not exist - IsADirectoryError: If path is a directory - """ + ) -> Content: + """Read contents of a file.""" resolved = self._resolve_path(path) if not resolved.exists(): - raise FileNotFoundError(f"File not found: {path}") + return Content(path=path, error=f"File not found: {path}") if resolved.is_dir(): - raise IsADirectoryError(f"Cannot read directory: {path}") + return Content(path=path, error=f"Cannot read directory: {path}") file_size = resolved.stat().st_size truncated = False @@ -210,20 +186,17 @@ def read_file( try: content = resolved.read_text(encoding="utf-8") except UnicodeDecodeError: - # Try with latin-1 for binary-ish files try: content = resolved.read_text(encoding="latin-1") except Exception: - raise ValueError(f"Cannot read file as text: {path}") + return Content(path=path, error=f"Cannot read file as text: {path}", size=file_size) total_lines = content.count("\n") + (1 if content and not content.endswith("\n") else 0) - # Truncate by bytes if len(content) > max_bytes: content = content[:max_bytes] truncated = True - # Truncate by lines if max_lines is not None: lines = content.split("\n") if len(lines) > max_lines: @@ -232,7 +205,7 @@ def read_file( rel_path = str(resolved.relative_to(self.root)) - return FileContent( + return Content( path=rel_path, content=content, size=file_size, @@ -246,20 +219,7 @@ def get_tree( max_depth: int = 3, include_hidden: bool = False, ) -> TreeNode: - """Get a tree representation of a directory. - - Args: - path: Relative path to directory - max_depth: Maximum depth to traverse - include_hidden: Whether to include hidden files - - Returns: - TreeNode representing the directory structure - - Raises: - FileNotFoundError: If path does not exist - NotADirectoryError: If path is not a directory - """ + """Get a tree representation of a directory.""" resolved = self._resolve_path(path) if not resolved.exists(): @@ -276,17 +236,6 @@ def _build_tree( include_hidden: bool, current_depth: int, ) -> TreeNode: - """Recursively build a tree structure. - - Args: - path: Current path - max_depth: Maximum depth - include_hidden: Include hidden files - current_depth: Current recursion depth - - Returns: - TreeNode for this directory - """ entry_type = EntryType.DIRECTORY if path.is_dir() else EntryType.FILE children: list[TreeNode] = [] @@ -296,48 +245,86 @@ def _build_tree( path.iterdir(), key=lambda x: (x.is_file(), x.name.lower()), ) - for entry in entries: - # Skip hidden files if not include_hidden and entry.name.startswith("."): continue - - # Skip common uninteresting directories if entry.is_dir() and entry.name in self.SKIP_DIRS: continue - - child = self._build_tree( - entry, - max_depth, - include_hidden, - current_depth + 1, - ) + child = self._build_tree(entry, max_depth, include_hidden, current_depth + 1) children.append(child) - except PermissionError: pass - return TreeNode( - name=path.name or str(path), - entry_type=entry_type, - children=children, - ) + return TreeNode(name=path.name or str(path), entry_type=entry_type, children=children) - def get_tree_string( + # ------------------------------------------------------------------ + # Write operations + # ------------------------------------------------------------------ + + def write_file( self, - path: str = "", - max_depth: int = 3, - include_hidden: bool = False, - ) -> str: - """Get a string representation of the directory tree. + path: str, + content: str, + content_type: str = "text/plain", + ) -> OperationResult: + """Write text content to a file. + + Creates parent directories as needed. Args: - path: Relative path to directory - max_depth: Maximum depth to traverse - include_hidden: Whether to include hidden files + path: Relative file path to write to. + content: Text content to write (UTF-8). + content_type: Ignored for local files; accepted for interface compatibility. Returns: - String representation of the tree + OperationResult indicating success or failure. """ - tree = self.get_tree(path, max_depth, include_hidden) - return tree.to_string() \ No newline at end of file + try: + resolved = self._resolve_path(path) + except ValueError as e: + return OperationResult(success=False, path=path, error=str(e)) + + if not path: + return OperationResult(success=False, path=path, error="Cannot write: path is empty") + + try: + resolved.parent.mkdir(parents=True, exist_ok=True) + resolved.write_text(content, encoding="utf-8") + return OperationResult( + success=True, + path=str(resolved.relative_to(self.root)), + message=f"Written {len(content.encode('utf-8'))} bytes to {resolved}", + ) + except Exception as e: + logger.error(f"write_file failed for {path!r}: {e}") + return OperationResult(success=False, path=path, error=str(e)) + + def delete_file(self, path: str) -> OperationResult: + """Delete a file. + + Args: + path: Relative file path to delete. + + Returns: + OperationResult indicating success or failure. + """ + try: + resolved = self._resolve_path(path) + except ValueError as e: + return OperationResult(success=False, path=path, error=str(e)) + + if not resolved.exists(): + return OperationResult(success=False, path=path, error=f"File not found: {path}") + if resolved.is_dir(): + return OperationResult(success=False, path=path, error=f"Path is a directory: {path}") + + try: + resolved.unlink() + return OperationResult( + success=True, + path=path, + message=f"Deleted {resolved}", + ) + except Exception as e: + logger.error(f"delete_file failed for {path!r}: {e}") + return OperationResult(success=False, path=path, error=str(e)) diff --git a/src/codespy/tools/filesystem/server.py b/src/codespy/tools/storage/filesystem/server.py similarity index 85% rename from src/codespy/tools/filesystem/server.py rename to src/codespy/tools/storage/filesystem/server.py index 2173351..295cda4 100644 --- a/src/codespy/tools/filesystem/server.py +++ b/src/codespy/tools/storage/filesystem/server.py @@ -3,12 +3,12 @@ import logging import os import sys +from collections import OrderedDict from functools import lru_cache -from pathlib import Path from mcp.server.fastmcp import FastMCP -from codespy.tools.filesystem.client import FileSystem +from codespy.tools.storage.filesystem.client import FileSystem logger = logging.getLogger(__name__) @@ -18,6 +18,10 @@ mcp = FastMCP("filesystem") _fs: FileSystem | None = None +# Manual cache for read_file to skip caching error results +_READ_FILE_CACHE_MAX = 512 +_read_file_cache: OrderedDict[tuple[str, int, int | None], tuple] = OrderedDict() + def _get_fs() -> FileSystem: """Get the FileSystem instance, raising if not initialized.""" @@ -26,11 +30,19 @@ def _get_fs() -> FileSystem: return _fs -@lru_cache(maxsize=256) def _read_file_cached(path: str, max_bytes: int, max_lines: int | None) -> tuple: - """Cached version of read_file.""" + """Cached version of read_file that doesn't cache error results.""" + key = (path, max_bytes, max_lines) + if key in _read_file_cache: + _read_file_cache.move_to_end(key) + return _read_file_cache[key] result = _get_fs().read_file(path, max_bytes, max_lines) - return tuple(sorted(result.model_dump().items())) + dumped = tuple(sorted(result.model_dump().items())) + if not result.error: + _read_file_cache[key] = dumped + if len(_read_file_cache) > _READ_FILE_CACHE_MAX: + _read_file_cache.popitem(last=False) + return dumped @mcp.tool() @@ -148,7 +160,7 @@ def get_file_info(path: str = "") -> dict: # Suppress noisy MCP server "Processing request" logs logging.getLogger("mcp.server").setLevel(logging.WARNING) logging.getLogger("mcp.server.lowlevel").setLevel(logging.WARNING) - + root = sys.argv[1] if len(sys.argv) > 1 else "." _fs = FileSystem(root) mcp.run() diff --git a/src/codespy/tools/filesystem/models.py b/src/codespy/tools/storage/models.py similarity index 55% rename from src/codespy/tools/filesystem/models.py rename to src/codespy/tools/storage/models.py index 1d81c07..01e41ba 100644 --- a/src/codespy/tools/filesystem/models.py +++ b/src/codespy/tools/storage/models.py @@ -1,21 +1,23 @@ -"""Data models for filesystem operations.""" +"""Shared data models for storage operations (filesystem and S3).""" + +from __future__ import annotations from datetime import datetime -from enum import Enum +from enum import StrEnum from pathlib import Path from pydantic import BaseModel, Field -class EntryType(str, Enum): - """Type of filesystem entry.""" +class EntryType(StrEnum): + """Type of storage entry.""" FILE = "file" DIRECTORY = "directory" SYMLINK = "symlink" -class FileInfo(BaseModel): +class Info(BaseModel): """Information about a file or directory.""" path: str = Field(description="Relative path from root") @@ -24,17 +26,20 @@ class FileInfo(BaseModel): size: int = Field(default=0, description="Size in bytes (0 for directories)") modified_at: datetime | None = Field(default=None, description="Last modified time") extension: str = Field(default="", description="File extension (empty for directories)") + # S3-specific (empty strings for local filesystem) + etag: str = Field(default="", description="ETag (S3 only)") + storage_class: str = Field(default="", description="S3 storage class (S3 only)") @classmethod - def from_path(cls, path: Path, root: Path) -> "FileInfo": - """Create FileInfo from a Path object. + def from_path(cls, path: Path, root: Path) -> Info: + """Create Info from a local filesystem Path. Args: - path: The file path - root: Root directory to compute relative path + path: The file path. + root: Root directory to compute relative path. Returns: - FileInfo instance + Info instance. """ stat = path.stat() rel_path = str(path.relative_to(root)) @@ -56,19 +61,20 @@ def from_path(cls, path: Path, root: Path) -> "FileInfo": ) -class DirectoryEntry(BaseModel): +class Entry(BaseModel): """Entry in a directory listing.""" name: str = Field(description="Entry name") entry_type: EntryType = Field(description="Type of entry") - size: int = Field(default=0, description="Size in bytes") + size: int = Field(default=0, description="Size in bytes (0 for directories)") + modified_at: datetime | None = Field(default=None, description="Last modified time (UTC)") -class DirectoryListing(BaseModel): +class Listing(BaseModel): """Result of listing a directory.""" path: str = Field(description="Directory path") - entries: list[DirectoryEntry] = Field(default_factory=list, description="Directory contents") + entries: list[Entry] = Field(default_factory=list, description="Directory contents") total_files: int = Field(default=0, description="Number of files") total_directories: int = Field(default=0, description="Number of directories") @@ -78,17 +84,17 @@ class TreeNode(BaseModel): name: str = Field(description="Entry name") entry_type: EntryType = Field(description="Type of entry") - children: list["TreeNode"] = Field(default_factory=list, description="Child nodes") + children: list[TreeNode] = Field(default_factory=list, description="Child nodes") def to_string(self, prefix: str = "", is_last: bool = True) -> str: """Convert tree node to string representation. Args: - prefix: Current line prefix - is_last: Whether this is the last sibling + prefix: Current line prefix. + is_last: Whether this is the last sibling. Returns: - String representation of the tree + String representation of the tree. """ connector = "└── " if is_last else "├── " icon = "📁 " if self.entry_type == EntryType.DIRECTORY else "📄 " @@ -101,11 +107,27 @@ def to_string(self, prefix: str = "", is_last: bool = True) -> str: return result -class FileContent(BaseModel): +class Content(BaseModel): """Result of reading a file.""" path: str = Field(description="File path") - content: str = Field(description="File content") - size: int = Field(description="Total file size in bytes") - lines: int = Field(description="Total number of lines") - truncated: bool = Field(default=False, description="Whether content was truncated") \ No newline at end of file + content: str = Field(default="", description="File content as text") + size: int = Field(default=0, description="Total file size in bytes") + lines: int = Field(default=0, description="Total number of lines") + truncated: bool = Field(default=False, description="Whether content was truncated") + content_type: str = Field(default="", description="MIME type (S3) or empty for local files") + error: str | None = Field(default=None, description="Error message if read failed") + + @property + def success(self) -> bool: + """Check if the file was read successfully.""" + return self.error is None + + +class OperationResult(BaseModel): + """Result of a write or delete operation.""" + + success: bool = Field(description="Whether the operation succeeded") + path: str = Field(description="File path that was operated on") + message: str = Field(default="", description="Human-readable result message") + error: str | None = Field(default=None, description="Error message if operation failed") diff --git a/src/codespy/tools/storage/s3/__init__.py b/src/codespy/tools/storage/s3/__init__.py new file mode 100644 index 0000000..73d2af2 --- /dev/null +++ b/src/codespy/tools/storage/s3/__init__.py @@ -0,0 +1,5 @@ +"""S3 storage backend.""" + +from codespy.tools.storage.s3.client import S3Client + +__all__ = ["S3Client"] diff --git a/src/codespy/tools/storage/s3/client.py b/src/codespy/tools/storage/s3/client.py new file mode 100644 index 0000000..4b5883f --- /dev/null +++ b/src/codespy/tools/storage/s3/client.py @@ -0,0 +1,425 @@ +"""S3 client for filesystem-like operations over a single bucket.""" + +from __future__ import annotations + +import logging +import posixpath +from urllib.parse import unquote + +from codespy.tools.storage.base import Storage +from codespy.tools.storage.models import ( + Content, + Entry, + EntryType, + Info, + Listing, + OperationResult, + TreeNode, +) + +logger = logging.getLogger(__name__) + + +class S3Client(Storage): + """Client for S3 operations rooted at a single bucket. + + Treats S3 key prefixes as directories and individual object keys as files, + mirroring the FileSystem client interface. All path arguments are relative + to the bucket root (no leading slash needed). + + Authentication uses the standard boto3 credential chain: + env vars (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY), ~/.aws/credentials, + IAM instance role, etc. + """ + + def __init__( + self, + bucket: str, + region: str | None = None, + endpoint_url: str | None = None, + ) -> None: + """Initialize the S3 client rooted at a bucket. + + Args: + bucket: S3 bucket name — all operations are scoped to this bucket. + region: AWS region (e.g. 'us-east-1'). Falls back to boto3 defaults. + endpoint_url: Custom endpoint URL for S3-compatible stores (e.g. MinIO). + """ + import boto3 # type: ignore[import-untyped] + + self.bucket = bucket + + kwargs: dict = {} + if region: + kwargs["region_name"] = region + if endpoint_url: + kwargs["endpoint_url"] = endpoint_url + + self._s3 = boto3.client("s3", **kwargs) + + # ------------------------------------------------------------------ + # Internal helpers + # ------------------------------------------------------------------ + + def _resolve_path(self, path: str) -> str: + """Resolve and validate a relative path for use as an S3 key. + + Rejects path traversal attempts including percent-encoded variants + (e.g. %2e%2e, %2f..%2f). Returns the normalized original path, + preserving S3 key semantics. + """ + # Decode percent-encoded characters for security validation only. + # This catches encoded traversal attempts (e.g. %2e%2e -> ..) + # without altering the returned S3 key. + decoded = unquote(path) + decoded_stripped = decoded.strip("/") + if any(part == ".." for part in decoded_stripped.split("/")): + raise ValueError(f"Path escapes bucket root: {path!r}") + + # Normalize the original (non-decoded) path for the actual S3 key + stripped = path.strip("/") + normalised = posixpath.normpath(stripped) + if normalised == ".": + return "" + # Belt-and-suspenders: catch anything normpath might produce + if normalised.startswith(".."): + raise ValueError(f"Path escapes bucket root: {path!r}") + return normalised + + def _file_name(self, path: str) -> str: + return path.rstrip("/").rsplit("/", 1)[-1] + + def _file_extension(self, path: str) -> str: + name = self._file_name(path) + if "." in name: + return name.rsplit(".", 1)[-1] + return "" + + def verify_access(self) -> None: + """Verify S3 bucket is accessible (credentials + bucket existence). + + Raises: + ClientError: If the bucket doesn't exist, credentials are invalid, + or IAM lacks ListBucket permission. Let the caller handle this. + """ + # Let ClientError propagate — NoSuchBucket, AccessDenied, InvalidAccessKeyId, etc. + self._s3.list_objects_v2(Bucket=self.bucket, MaxKeys=1) + + def _client_error_code(self, exc: Exception) -> str: + try: + return exc.response["Error"]["Code"] # type: ignore[attr-defined] + except Exception: + return "" + + # ------------------------------------------------------------------ + # Read operations + # ------------------------------------------------------------------ + + def exists(self, path: str = "") -> bool: + """Check whether a file or directory exists in the bucket.""" + file_path = self._resolve_path(path) + + if not file_path: + return True + + try: + self._s3.head_object(Bucket=self.bucket, Key=file_path) + return True + except Exception as e: + if self._client_error_code(e) not in ("404", "NoSuchKey"): + logger.warning(f"HeadObject error for {file_path!r}: {e}") + + dir_prefix = file_path if file_path.endswith("/") else file_path + "/" + try: + resp = self._s3.list_objects_v2( + Bucket=self.bucket, + Prefix=dir_prefix, + MaxKeys=1, + ) + return bool(resp.get("Contents") or resp.get("CommonPrefixes")) + except Exception as e: + logger.warning(f"ListObjectsV2 error for prefix {dir_prefix!r}: {e}") + return False + + def get_info(self, path: str = "") -> Info: + """Get metadata about a file or directory.""" + file_path = self._resolve_path(path) + + if not file_path: + return Info( + path=".", + name=self.bucket, + entry_type=EntryType.DIRECTORY, + ) + + try: + resp = self._s3.head_object(Bucket=self.bucket, Key=file_path) + return Info( + path=file_path, + name=self._file_name(file_path), + entry_type=EntryType.FILE, + size=resp.get("ContentLength", 0), + modified_at=resp.get("LastModified"), + extension=self._file_extension(file_path), + etag=resp.get("ETag", "").strip('"'), + storage_class=resp.get("StorageClass", "STANDARD"), + ) + except Exception as e: + if self._client_error_code(e) not in ("404", "NoSuchKey"): + raise + + dir_prefix = file_path if file_path.endswith("/") else file_path + "/" + resp = self._s3.list_objects_v2( + Bucket=self.bucket, + Prefix=dir_prefix, + MaxKeys=1, + ) + if resp.get("Contents") or resp.get("CommonPrefixes"): + return Info( + path=file_path, + name=self._file_name(file_path), + entry_type=EntryType.DIRECTORY, + ) + + raise FileNotFoundError(f"Path not found in bucket {self.bucket!r}: {file_path!r}") + + def list_directory( + self, + path: str = "", + include_hidden: bool = False, + ) -> Listing: + """List files and subdirectories directly under a path (one level deep).""" + dir_path = self._resolve_path(path) + prefix = (dir_path + "/") if dir_path else "" + + entries: list[Entry] = [] + total_files = 0 + total_directories = 0 + continuation_token: str | None = None + + while True: + kwargs: dict = { + "Bucket": self.bucket, + "Delimiter": "/", + "Prefix": prefix, + "MaxKeys": 1000, + } + if continuation_token: + kwargs["ContinuationToken"] = continuation_token + + try: + resp = self._s3.list_objects_v2(**kwargs) + except Exception as e: + logger.error(f"Error listing {prefix!r}: {e}") + break + + for cp in resp.get("CommonPrefixes", []): + sub = cp.get("Prefix", "") + name = sub.rstrip("/").rsplit("/", 1)[-1] + if not include_hidden and name.startswith("."): + continue + entries.append(Entry(name=name, entry_type=EntryType.DIRECTORY)) + total_directories += 1 + + for obj in resp.get("Contents", []): + file_key = obj.get("Key", "") + if file_key == prefix: + continue + name = file_key.rsplit("/", 1)[-1] + if not include_hidden and name.startswith("."): + continue + entries.append( + Entry( + name=name, + entry_type=EntryType.FILE, + size=obj.get("Size", 0), + modified_at=obj.get("LastModified"), # boto3 returns tz-aware datetime + ) + ) + total_files += 1 + + if not resp.get("IsTruncated"): + break + continuation_token = resp.get("NextContinuationToken") + + entries.sort(key=lambda e: (e.entry_type == EntryType.FILE, e.name.lower())) + + return Listing( + path=dir_path or ".", + entries=entries, + total_files=total_files, + total_directories=total_directories, + ) + + def read_file( + self, + path: str, + max_bytes: int = 100_000, + max_lines: int | None = None, + ) -> Content: + """Read a file from S3 as text.""" + file_path = self._resolve_path(path) + if not file_path: + return Content(path=path, error="Cannot read: path is empty (bucket root)") + + try: + resp = self._s3.get_object(Bucket=self.bucket, Key=file_path) + except Exception as e: + return Content(path=file_path, error=f"GetObject failed: {e}") + + size: int = resp.get("ContentLength", 0) + content_type: str = resp.get("ContentType", "") + truncated = False + + try: + raw: bytes = resp["Body"].read() + except Exception as e: + return Content(path=file_path, error=f"Error reading body: {e}", size=size, content_type=content_type) + + if len(raw) > max_bytes: + truncated = True + raw = raw[:max_bytes] + # Ensure truncation didn't split a multi-byte UTF-8 sequence. + # Only the last 1-3 bytes can be an incomplete character. + try: + raw.decode("utf-8") + except UnicodeDecodeError as e: + # Only trim if the error is at the truncation boundary (last 4 bytes) + if e.start >= len(raw) - 4: + raw = raw[:e.start] + # Interior errors are handled by the full decode below (latin-1 fallback) + + try: + content = raw.decode("utf-8") + except UnicodeDecodeError: + try: + content = raw.decode("latin-1") + except Exception: + return Content( + path=file_path, + error="Cannot decode file as text (binary content)", + size=size, + content_type=content_type, + ) + + total_lines = content.count("\n") + (1 if content and not content.endswith("\n") else 0) + + if max_lines is not None: + lines = content.split("\n") + if len(lines) > max_lines: + content = "\n".join(lines[:max_lines]) + truncated = True + + return Content( + path=file_path, + content=content, + size=size, + lines=total_lines, + truncated=truncated, + content_type=content_type, + ) + + def get_tree( + self, + path: str = "", + max_depth: int = 3, + include_hidden: bool = False, + ) -> TreeNode: + """Get a tree representation of a directory in the bucket.""" + dir_path = self._resolve_path(path) + name = self._file_name(dir_path) if dir_path else self.bucket + return self._build_tree(dir_path, name, max_depth, include_hidden, 0) + + def _build_tree( + self, + dir_path: str, + name: str, + max_depth: int, + include_hidden: bool, + current_depth: int, + ) -> TreeNode: + if current_depth >= max_depth: + return TreeNode(name=name, entry_type=EntryType.DIRECTORY) + + listing = self.list_directory(dir_path, include_hidden=include_hidden) + children: list[TreeNode] = [] + + for entry in listing.entries: + if entry.entry_type == EntryType.DIRECTORY: + child_path = f"{dir_path}/{entry.name}" if dir_path else entry.name + child = self._build_tree(child_path, entry.name, max_depth, include_hidden, current_depth + 1) + else: + child = TreeNode(name=entry.name, entry_type=EntryType.FILE) + children.append(child) + + return TreeNode(name=name, entry_type=EntryType.DIRECTORY, children=children) + + # ------------------------------------------------------------------ + # Write operations + # ------------------------------------------------------------------ + + def write_file( + self, + path: str, + content: str, + content_type: str = "text/plain", + ) -> OperationResult: + """Write text content to a file in the bucket.""" + file_path = self._resolve_path(path) + if not file_path: + return OperationResult( + success=False, + path=path, + error="Cannot write: path resolves to bucket root", + ) + + try: + body = content.encode("utf-8") + self._s3.put_object( + Bucket=self.bucket, + Key=file_path, + Body=body, + ContentType=content_type, + ContentLength=len(body), + ) + return OperationResult( + success=True, + path=file_path, + message=f"Written {len(body)} bytes to s3://{self.bucket}/{file_path}", + ) + except Exception as e: + logger.error(f"PutObject failed for {file_path!r}: {e}") + return OperationResult(success=False, path=file_path, error=str(e)) + + def delete_file(self, path: str) -> OperationResult: + """Delete a file from the bucket.""" + file_path = self._resolve_path(path) + if not file_path: + return OperationResult( + success=False, + path=path, + error="Cannot delete: path resolves to bucket root", + ) + + # Check existence first (matches filesystem semantics) + try: + self._s3.head_object(Bucket=self.bucket, Key=file_path) + except self._s3.exceptions.ClientError as e: + if self._client_error_code(e) == "404": + return OperationResult( + success=False, + path=file_path, + error=f"File not found: {path}", + ) + raise + + try: + self._s3.delete_object(Bucket=self.bucket, Key=file_path) + return OperationResult( + success=True, + path=file_path, + message=f"Deleted s3://{self.bucket}/{file_path}", + ) + except Exception as e: + logger.error(f"DeleteObject failed for {file_path!r}: {e}") + return OperationResult(success=False, path=file_path, error=str(e)) diff --git a/src/codespy/tools/storage/s3/server.py b/src/codespy/tools/storage/s3/server.py new file mode 100644 index 0000000..dc71e47 --- /dev/null +++ b/src/codespy/tools/storage/s3/server.py @@ -0,0 +1,237 @@ +"""MCP server for S3 filesystem-like operations.""" + +import logging +import os +import sys +from collections import OrderedDict +from functools import lru_cache + +from mcp.server.fastmcp import FastMCP + +from codespy.tools.storage.s3.client import S3Client + +logger = logging.getLogger(__name__) + +# Get caller module from environment (set by mcp_utils.py) +_caller_module = os.environ.get("MCP_CALLER_MODULE", "unknown") + +mcp = FastMCP("s3") +_client: S3Client | None = None + +# Manual cache for read_file to skip caching error results +_READ_FILE_CACHE_MAX = 512 +_read_file_cache: OrderedDict[tuple[str, int, int | None], tuple] = OrderedDict() + + +def _get_client() -> S3Client: + """Get the S3Client instance, raising if not initialized.""" + if _client is None: + raise RuntimeError("S3Client not initialized") + return _client + + +# ------------------------------------------------------------------ +# Read tools (cached, like filesystem/server.py) +# ------------------------------------------------------------------ + + +@lru_cache(maxsize=512) +def _file_exists_cached(path: str) -> bool: + """Cached version of exists.""" + return _get_client().exists(path) + + +@mcp.tool() +def file_exists(path: str = "") -> bool: + """Check if a file or directory exists in the bucket. + + Args: + path: Relative path to check (empty = bucket root) + + Returns: + True if the path exists + """ + client = _get_client() + logger.info(f"[S3] {_caller_module} -> file_exists: s3://{client.bucket}/{path}") + return _file_exists_cached(path) + + +@lru_cache(maxsize=256) +def _get_file_info_cached(path: str) -> tuple: + """Cached version of get_info.""" + result = _get_client().get_info(path) + return tuple(sorted(result.model_dump().items())) + + +@mcp.tool() +def get_file_info(path: str = "") -> dict: + """Get information about a file or directory. + + Args: + path: Relative path (empty = bucket root) + + Returns: + Dict with path, name, entry_type, size, modified_at, extension, etag, storage_class + """ + client = _get_client() + logger.info(f"[S3] {_caller_module} -> get_file_info: s3://{client.bucket}/{path}") + return dict(_get_file_info_cached(path)) + + +@lru_cache(maxsize=256) +def _list_directory_cached(path: str, include_hidden: bool) -> tuple: + """Cached version of list_directory.""" + result = _get_client().list_directory(path, include_hidden) + return tuple(sorted(result.model_dump().items())) + + +@mcp.tool() +def list_directory(path: str = "", include_hidden: bool = False) -> dict: + """List files and subdirectories directly under a path (one level deep). + + Args: + path: Relative directory path (empty = bucket root) + include_hidden: Whether to include entries starting with '.' + + Returns: + Dict with path, entries, total_files, total_directories + """ + client = _get_client() + logger.info(f"[S3] {_caller_module} -> list_directory: s3://{client.bucket}/{path}") + return dict(_list_directory_cached(path, include_hidden)) + + +@lru_cache(maxsize=128) +def _get_tree_cached(path: str, max_depth: int, include_hidden: bool) -> str: + """Cached version of get_tree_string.""" + return _get_client().get_tree_string(path, max_depth, include_hidden) + + +@mcp.tool() +def get_tree(path: str = "", max_depth: int = 3, include_hidden: bool = False) -> str: + """Get a string representation of the directory tree. + + Args: + path: Relative directory path (empty = bucket root) + max_depth: Maximum depth to traverse + include_hidden: Whether to include entries starting with '.' + + Returns: + String representation of the directory tree + """ + client = _get_client() + logger.info( + f"[S3] {_caller_module} -> get_tree: s3://{client.bucket}/{path} (depth={max_depth})" + ) + return _get_tree_cached(path, max_depth, include_hidden) + + +def _read_file_cached(path: str, max_bytes: int, max_lines: int | None) -> tuple: + """Cached version of read_file that doesn't cache error results.""" + key = (path, max_bytes, max_lines) + if key in _read_file_cache: + _read_file_cache.move_to_end(key) + return _read_file_cache[key] + result = _get_client().read_file(path, max_bytes, max_lines) + dumped = tuple(sorted(result.model_dump().items())) + if not result.error: + _read_file_cache[key] = dumped + if len(_read_file_cache) > _READ_FILE_CACHE_MAX: + _read_file_cache.popitem(last=False) + return dumped + + +@mcp.tool() +def read_file(path: str, max_bytes: int = 100_000, max_lines: int | None = None) -> dict: + """Read a file from the bucket as text. + + Args: + path: Relative file path + max_bytes: Maximum bytes to read (default 100 KB) + max_lines: Maximum lines to read (optional) + + Returns: + Dict with path, content, size, lines, truncated, content_type, error (if any) + """ + client = _get_client() + logger.info(f"[S3] {_caller_module} -> read_file: s3://{client.bucket}/{path}") + return dict(_read_file_cached(path, max_bytes, max_lines)) + + +# ------------------------------------------------------------------ +# Cache invalidation +# ------------------------------------------------------------------ + + +def _invalidate_read_caches() -> None: + """Clear all cached read results after a mutating operation. + + lru_cache has no per-key eviction, and a single write or delete can + invalidate entries across several caches - including listings and trees + for every ancestor prefix - so all read caches are cleared wholesale. + Called unconditionally, since a failed put/delete may still have changed + bucket state (timeouts, partial uploads, ambiguous retries). + """ + _file_exists_cached.cache_clear() + _get_file_info_cached.cache_clear() + _list_directory_cached.cache_clear() + _get_tree_cached.cache_clear() + _read_file_cache.clear() + + +# ------------------------------------------------------------------ +# Write tools (not cached) +# ------------------------------------------------------------------ + + +@mcp.tool() +def write_file(path: str, content: str, content_type: str = "text/plain") -> dict: + """Write text content to a file in the bucket. + + Args: + path: Relative file path to write to + content: Text content to write (encoded as UTF-8) + content_type: MIME type for the object (default 'text/plain') + + Returns: + Dict with success, path, message, error (if any) + """ + client = _get_client() + logger.info(f"[S3] {_caller_module} -> write_file: s3://{client.bucket}/{path}") + result = client.write_file(path, content, content_type) + _invalidate_read_caches() + return result.model_dump() + + +@mcp.tool() +def delete_file(path: str) -> dict: + """Delete a file from the bucket. + + Args: + path: Relative file path to delete + + Returns: + Dict with success, path, message, error (if any) + """ + client = _get_client() + logger.info(f"[S3] {_caller_module} -> delete_file: s3://{client.bucket}/{path}") + result = client.delete_file(path) + _invalidate_read_caches() + return result.model_dump() + + +if __name__ == "__main__": + # Suppress noisy MCP server "Processing request" logs + logging.getLogger("mcp.server").setLevel(logging.WARNING) + logging.getLogger("mcp.server.lowlevel").setLevel(logging.WARNING) + + if len(sys.argv) < 2: + print("Usage: python server.py [region] [endpoint_url]", file=sys.stderr) + sys.exit(1) + + bucket = sys.argv[1] + region = sys.argv[2] if len(sys.argv) > 2 else os.environ.get("AWS_DEFAULT_REGION") + endpoint_url = sys.argv[3] if len(sys.argv) > 3 else os.environ.get("S3_ENDPOINT_URL") + + _client = S3Client(bucket=bucket, region=region, endpoint_url=endpoint_url) + mcp.run() diff --git a/src/codespy/tools/web/__init__.py b/src/codespy/tools/web/__init__.py index 9443bc6..9207777 100644 --- a/src/codespy/tools/web/__init__.py +++ b/src/codespy/tools/web/__init__.py @@ -8,4 +8,4 @@ "WebPage", "SearchResult", "SearchResults", -] \ No newline at end of file +] diff --git a/src/codespy/tools/web/client.py b/src/codespy/tools/web/client.py index a5a4d1d..683f548 100644 --- a/src/codespy/tools/web/client.py +++ b/src/codespy/tools/web/client.py @@ -292,4 +292,4 @@ def search_and_fetch(self, query: str, num_results: int = 3) -> list[WebPage]: page = self.fetch_page(result.url) pages.append(page) - return pages \ No newline at end of file + return pages diff --git a/src/codespy/tools/web/models.py b/src/codespy/tools/web/models.py index 4260174..a9a0733 100644 --- a/src/codespy/tools/web/models.py +++ b/src/codespy/tools/web/models.py @@ -55,4 +55,4 @@ def to_markdown(self) -> str: else: lines.append("") - return "\n".join(lines) \ No newline at end of file + return "\n".join(lines) diff --git a/src/codespy/tools/web/server.py b/src/codespy/tools/web/server.py index 05a4980..4bee6e6 100644 --- a/src/codespy/tools/web/server.py +++ b/src/codespy/tools/web/server.py @@ -72,6 +72,6 @@ def search_and_fetch(query: str, num_results: int = 3) -> list[dict]: # Suppress noisy MCP server "Processing request" logs logging.getLogger("mcp.server").setLevel(logging.WARNING) logging.getLogger("mcp.server.lowlevel").setLevel(logging.WARNING) - + _browser = WebBrowser() mcp.run() diff --git a/tests/test_config_memory.py b/tests/test_config_memory.py new file mode 100644 index 0000000..33488d1 --- /dev/null +++ b/tests/test_config_memory.py @@ -0,0 +1,217 @@ +"""Tests for memory storage access verification.""" + +import sys +from unittest.mock import MagicMock, patch + +import pytest + +from codespy.config_memory import verify_memory_access +from codespy.tools.storage.filesystem.client import FileSystem +from codespy.tools.storage.s3.client import S3Client + + +class TestVerifyMemoryAccess: + """Tests for verify_memory_access function.""" + + def test_verify_memory_access_all_disabled(self): + """When all signatures disabled, returns success with skip message.""" + settings = MagicMock() + settings.is_signature_enabled.return_value = False + settings.get_memory_enabled.return_value = False + + success, message = verify_memory_access(settings) + + assert success is True + assert "Memory disabled" in message + + def test_verify_memory_access_enabled_sig_disabled(self): + """When signature has memory enabled but signature itself is disabled.""" + settings = MagicMock() + + def is_enabled(sig): + return False # All signatures disabled + + def memory_enabled(sig): + return True # But memory is configured + + settings.is_signature_enabled.side_effect = is_enabled + settings.get_memory_enabled.side_effect = memory_enabled + + success, message = verify_memory_access(settings) + + assert success is True + assert "Memory disabled" in message + + def test_verify_memory_access_store_none(self): + """When memory active but store is None (missing S3 bucket).""" + settings = MagicMock() + settings.memory.backend = "s3" + + def is_enabled(sig): + return sig == "summary" # Only summary enabled + + def memory_enabled(sig): + return sig == "summary" # Memory enabled for summary + + settings.is_signature_enabled.side_effect = is_enabled + settings.get_memory_enabled.side_effect = memory_enabled + + with patch("codespy.config_memory.get_memory_store", return_value=None): + success, message = verify_memory_access(settings) + + assert success is False + assert "not configured" in message + + def test_verify_memory_access_filesystem_ok(self, tmp_path): + """When memory active with valid filesystem store.""" + settings = MagicMock() + settings.memory.backend = "filesystem" + + def is_enabled(sig): + return sig == "summary" + + def memory_enabled(sig): + return sig == "summary" + + settings.is_signature_enabled.side_effect = is_enabled + settings.get_memory_enabled.side_effect = memory_enabled + + # Create a real FileSystem with tmp_path + fs = FileSystem(tmp_path) + + with patch("codespy.config_memory.get_memory_store", return_value=fs): + success, message = verify_memory_access(settings) + + assert success is True + assert "verified" in message + assert "filesystem" in message + + def test_verify_memory_access_s3_ok(self): + """When memory active with S3 store that verifies successfully.""" + settings = MagicMock() + settings.memory.backend = "s3" + + def is_enabled(sig): + return sig == "summary" + + def memory_enabled(sig): + return sig == "summary" + + settings.is_signature_enabled.side_effect = is_enabled + settings.get_memory_enabled.side_effect = memory_enabled + + # Mock S3 client that verifies successfully + mock_store = MagicMock() + mock_store.verify_access.return_value = None + + with patch("codespy.config_memory.get_memory_store", return_value=mock_store): + success, message = verify_memory_access(settings) + + assert success is True + assert "verified" in message + assert "s3" in message + mock_store.verify_access.assert_called_once() + + def test_verify_memory_access_raises(self): + """When store's verify_access raises an exception.""" + settings = MagicMock() + settings.memory.backend = "filesystem" + + def is_enabled(sig): + return sig == "summary" + + def memory_enabled(sig): + return sig == "summary" + + settings.is_signature_enabled.side_effect = is_enabled + settings.get_memory_enabled.side_effect = memory_enabled + + # Mock store that raises on verify_access + mock_store = MagicMock() + mock_store.verify_access.side_effect = PermissionError("Access denied") + + with patch("codespy.config_memory.get_memory_store", return_value=mock_store): + success, message = verify_memory_access(settings) + + assert success is False + assert "not accessible" in message + assert "Access denied" in message + + +class TestFileSystemVerifyAccess: + """Tests for FileSystem.verify_access method.""" + + def test_verify_access_filesystem_valid(self, tmp_path): + """Valid filesystem root - no exception raised.""" + fs = FileSystem(tmp_path) + # Should not raise + fs.verify_access() + + def test_verify_access_filesystem_deleted_root(self, tmp_path): + """Root deleted after initialization - raises FileNotFoundError.""" + fs = FileSystem(tmp_path) + # Delete the root directory + import shutil + + shutil.rmtree(tmp_path) + + with pytest.raises(FileNotFoundError, match="does not exist"): + fs.verify_access() + + @pytest.mark.skipif(sys.platform == "win32", reason="chmod 000 not supported on Windows") + def test_verify_access_filesystem_not_readable(self, tmp_path): + """Root not readable - raises PermissionError.""" + import os + + fs = FileSystem(tmp_path) + # Remove read permission + os.chmod(tmp_path, 0o000) + + try: + with pytest.raises(PermissionError, match="not readable"): + fs.verify_access() + finally: + # Restore permission for cleanup + os.chmod(tmp_path, 0o755) + + +class TestS3ClientVerifyAccess: + """Tests for S3Client.verify_access method.""" + + def test_verify_access_s3_success(self): + """Successful S3 access - no exception raised.""" + # Create S3Client without calling __init__ (no boto3) + client = S3Client.__new__(S3Client) + client.bucket = "test-bucket" + client._s3 = MagicMock() + client._s3.list_objects_v2.return_value = {} + + # Should not raise + client.verify_access() + + # Verify the call was made correctly + client._s3.list_objects_v2.assert_called_once_with(Bucket="test-bucket", MaxKeys=1) + + def test_verify_access_s3_client_error(self): + """S3 client error propagates.""" + from unittest.mock import MagicMock + + # Create S3Client without calling __init__ + client = S3Client.__new__(S3Client) + client.bucket = "test-bucket" + client._s3 = MagicMock() + + # Simulate boto3 ClientError + class ClientError(Exception): + def __init__(self, error_response, operation_name): + self.response = error_response + self.operation_name = operation_name + super().__init__(str(error_response)) + + client._s3.list_objects_v2.side_effect = ClientError( + {"Error": {"Code": "NoSuchBucket", "Message": "The specified bucket does not exist"}}, + "ListObjectsV2", + ) + + with pytest.raises(ClientError): + client.verify_access() diff --git a/tests/test_context_memory.py b/tests/test_context_memory.py new file mode 100644 index 0000000..aa69024 --- /dev/null +++ b/tests/test_context_memory.py @@ -0,0 +1,524 @@ +"""Tests for ContextMemory with Topics.""" + +from pathlib import Path + +import pytest + +from codespy.agents.memory.hippocampus import ( + ContextMemory, + Item, + Operation, + OpType, + Topic, + compute_common_ancestor_topic_id, + make_topic_id, +) +from codespy.agents.reviewer.modules.manifest_parser import extract_package_name + + +class TestMakeTopicId: + """Tests for make_topic_id function.""" + + def test_package_name_contains_repo(self): + """Path 1: package_name contains owner/repo -> use from owner/repo onwards.""" + package_name = "github.com/owner/repo/packages/auth" + result = make_topic_id("owner/repo", "packages/auth", package_name) + assert result == "owner/repo/packages/auth" + + def test_package_name_without_repo(self): + """Path 2: package_name provided but doesn't contain repo -> prepend repo.""" + package_name = "@myorg/auth-service" + result = make_topic_id("owner/repo", "packages/auth", package_name) + assert result == "owner/repo/@myorg/auth-service" + + def test_subroot_fallback(self): + """Path 3: no package_name -> use subroot.""" + result = make_topic_id("owner/repo", "packages/auth", None) + assert result == "owner/repo/packages/auth" + + def test_root_scope(self): + """Root scope (subroot='.') returns just repo_full_name.""" + result = make_topic_id("owner/repo", ".", None) + assert result == "owner/repo" + + def test_empty_subroot(self): + """Empty subroot returns just repo_full_name.""" + result = make_topic_id("owner/repo", "", None) + assert result == "owner/repo" + + def test_strips_leading_slashes(self): + """Leading slashes in subroot are stripped.""" + result = make_topic_id("owner/repo", "/packages/auth/", None) + assert result == "owner/repo/packages/auth" + + +class TestComputeCommonAncestorTopicId: + """Tests for compute_common_ancestor_topic_id function.""" + + def test_single_scope_returns_none(self): + """Single scope returns None (no common ancestor needed).""" + result = compute_common_ancestor_topic_id("owner/repo", ["packages/auth"]) + assert result is None + + def test_empty_list_returns_none(self): + """Empty list returns None.""" + result = compute_common_ancestor_topic_id("owner/repo", []) + assert result is None + + def test_two_scopes_with_common_ancestor(self): + """Two scopes sharing common ancestor.""" + subroots = ["packages/auth", "packages/api"] + result = compute_common_ancestor_topic_id("owner/repo", subroots) + assert result == "owner/repo/packages" + + def test_three_scopes_with_common_ancestor(self): + """Three scopes with nested common ancestor.""" + subroots = ["packages/auth/src", "packages/auth/tests", "packages/api"] + result = compute_common_ancestor_topic_id("owner/repo", subroots) + assert result == "owner/repo/packages" + + def test_disjoint_scopes_returns_root(self): + """Disjoint scopes with no common ancestor return root.""" + subroots = ["frontend", "backend"] + result = compute_common_ancestor_topic_id("owner/repo", subroots) + assert result == "owner/repo" + + def test_one_root_scope(self): + """Mix of root and nested scopes returns root.""" + subroots = [".", "packages/auth"] + result = compute_common_ancestor_topic_id("owner/repo", subroots) + assert result == "owner/repo" + + def test_identical_scopes(self): + """Identical scopes return that scope.""" + subroots = ["packages/auth", "packages/auth"] + result = compute_common_ancestor_topic_id("owner/repo", subroots) + assert result == "owner/repo/packages/auth" + + +class TestExtractPackageName: + """Tests for extract_package_name function.""" + + def test_extract_from_package_json(self, tmp_path: Path): + """Extract name from package.json.""" + manifest = tmp_path / "package.json" + manifest.write_text('{"name": "my-package", "version": "1.0.0"}') + result = extract_package_name("package.json", tmp_path) + assert result == "my-package" + + def test_extract_from_composer_json(self, tmp_path: Path): + """Extract name from composer.json.""" + manifest = tmp_path / "composer.json" + manifest.write_text('{"name": "vendor/package", "version": "1.0.0"}') + result = extract_package_name("composer.json", tmp_path) + assert result == "vendor/package" + + def test_extract_from_go_mod(self, tmp_path: Path): + """Extract module from go.mod.""" + manifest = tmp_path / "go.mod" + manifest.write_text("module github.com/owner/repo\n\ngo 1.21\n") + result = extract_package_name("go.mod", tmp_path) + assert result == "github.com/owner/repo" + + def test_extract_from_cargo_toml(self, tmp_path: Path): + """Extract name from Cargo.toml.""" + manifest = tmp_path / "Cargo.toml" + manifest.write_text('[package]\nname = "my-crate"\nversion = "1.0.0"') + result = extract_package_name("Cargo.toml", tmp_path) + assert result == "my-crate" + + def test_extract_from_pubspec_yaml(self, tmp_path: Path): + """Extract name from pubspec.yaml.""" + manifest = tmp_path / "pubspec.yaml" + manifest.write_text("name: my_app\ndescription: A Flutter app\n") + result = extract_package_name("pubspec.yaml", tmp_path) + assert result == "my_app" + + def test_extract_from_chart_yaml(self, tmp_path: Path): + """Extract name from Chart.yaml.""" + manifest = tmp_path / "Chart.yaml" + manifest.write_text("apiVersion: v2\nname: my-chart\nversion: 1.0.0\n") + result = extract_package_name("Chart.yaml", tmp_path) + assert result == "my-chart" + + def test_extract_from_setup_cfg(self, tmp_path: Path): + """Extract name from setup.cfg.""" + manifest = tmp_path / "setup.cfg" + manifest.write_text("[metadata]\nname = my-package\nversion = 1.0.0\n") + result = extract_package_name("setup.cfg", tmp_path) + assert result == "my-package" + + def test_pyproject_toml_project_name(self, tmp_path: Path): + """Extract name from pyproject.toml [project].""" + manifest = tmp_path / "pyproject.toml" + manifest.write_text('[project]\nname = "my-project"\nversion = "1.0.0"\n') + result = extract_package_name("pyproject.toml", tmp_path) + assert result == "my-project" + + def test_pyproject_toml_poetry_name(self, tmp_path: Path): + """Extract name from pyproject.toml [tool.poetry].""" + manifest = tmp_path / "pyproject.toml" + manifest.write_text('[tool.poetry]\nname = "poetry-project"\nversion = "1.0.0"\n') + result = extract_package_name("pyproject.toml", tmp_path) + assert result == "poetry-project" + + def test_missing_manifest_returns_none(self, tmp_path: Path): + """Missing manifest file returns None.""" + result = extract_package_name("package.json", tmp_path) + assert result is None + + def test_malformed_json_returns_none(self, tmp_path: Path): + """Malformed JSON returns None.""" + manifest = tmp_path / "package.json" + manifest.write_text("not valid json") + result = extract_package_name("package.json", tmp_path) + assert result is None + + def test_missing_name_field_returns_none(self, tmp_path: Path): + """JSON without name field returns None.""" + manifest = tmp_path / "package.json" + manifest.write_text('{"version": "1.0.0"}') + result = extract_package_name("package.json", tmp_path) + assert result is None + + def test_gradle_from_settings_gradle(self, tmp_path: Path): + """Extract project name from settings.gradle.""" + manifest = tmp_path / "build.gradle" + manifest.write_text("") + settings = tmp_path / "settings.gradle" + settings.write_text("rootProject.name = 'my-project'") + result = extract_package_name("build.gradle", tmp_path) + assert result == "my-project" + + +class TestContextMemoryRender: + """Tests for ContextMemory.render() method.""" + + def test_topic_grouped_format(self): + """Render produces topic-grouped format.""" + topics = [ + Topic(id="owner/repo/auth", description="Auth service"), + Topic(id="owner/repo/api", description="API gateway"), + ] + items = [ + Item(id="cu-abc", content="Auth uses JWT", topic_ids=["owner/repo/auth"]), + Item(id="cu-def", content="API uses rate limiting", topic_ids=["owner/repo/api"]), + ] + memory = ContextMemory( + topics=topics, + context_understanding=items, + ) + result = memory.render() + + assert "## TOPIC: owner/repo/auth (Auth service)" in result + assert "## TOPIC: owner/repo/api (API gateway)" in result + assert "[cu-abc] Auth uses JWT" in result + assert "[cu-def] API uses rate limiting" in result + + def test_shared_group_for_multiple_topic_ids(self): + """Items with 2+ topic_ids go to SHARED group.""" + topics = [ + Topic(id="owner/repo/auth", description="Auth service"), + Topic(id="owner/repo/api", description="API gateway"), + ] + items = [ + Item(id="cu-shared", content="Shared context", topic_ids=["owner/repo/auth", "owner/repo/api"]), + ] + memory = ContextMemory( + topics=topics, + context_understanding=items, + ) + result = memory.render() + + assert "## SHARED" in result + assert "[cu-shared] Shared context" in result + + def test_shared_group_for_empty_topic_ids(self): + """Items with empty topic_ids go to SHARED group.""" + topics = [Topic(id="owner/repo/auth", description="Auth service")] + items = [ + Item(id="cu-empty", content="No topic", topic_ids=[]), + ] + memory = ContextMemory( + topics=topics, + context_understanding=items, + ) + result = memory.render() + + assert "## SHARED" in result + assert "[cu-empty] No topic" in result + + def test_shared_group_for_unknown_topic_id(self): + """Items with unknown topic_id go to SHARED group.""" + topics = [Topic(id="owner/repo/auth", description="Auth service")] + items = [ + Item(id="cu-unknown", content="Unknown topic", topic_ids=["nonexistent"]), + ] + memory = ContextMemory( + topics=topics, + context_understanding=items, + ) + result = memory.render() + + assert "## SHARED" in result + assert "[cu-unknown] Unknown topic" in result + + def test_empty_memory_returns_empty_string(self): + """Completely empty memory returns empty string.""" + memory = ContextMemory() + result = memory.render() + assert result == "" + + def test_topic_with_no_items_is_hidden(self): + """Topics with no items are not rendered.""" + topics = [ + Topic(id="owner/repo/auth", description="Auth service"), + Topic(id="owner/repo/empty", description="Empty scope"), + ] + items = [ + Item(id="cu-abc", content="Only auth has items", topic_ids=["owner/repo/auth"]), + ] + memory = ContextMemory( + topics=topics, + context_understanding=items, + ) + result = memory.render() + + assert "owner/repo/auth" in result + assert "owner/repo/empty" not in result + + def test_section_headers_in_render(self): + """Render includes section headers for non-empty sections.""" + topics = [Topic(id="owner/repo/auth", description="Auth service")] + memory = ContextMemory( + topics=topics, + context_roadmap=[Item(id="cr-1", content="Roadmap item", topic_ids=["owner/repo/auth"])], + context_understanding=[Item(id="cu-1", content="Understanding item", topic_ids=["owner/repo/auth"])], + ) + result = memory.render() + + assert "### CONTEXT ROADMAP" in result + assert "### CONTEXT UNDERSTANDING" in result + + def test_shared_appears_first(self): + """SHARED group appears before topic groups.""" + topics = [Topic(id="owner/repo/auth", description="Auth service")] + memory = ContextMemory( + topics=topics, + context_understanding=[ + Item(id="cu-1", content="Topic item", topic_ids=["owner/repo/auth"]), + Item(id="cu-2", content="Shared item", topic_ids=[]), + ], + ) + result = memory.render() + + shared_pos = result.find("## SHARED") + topic_pos = result.find("## TOPIC") + assert shared_pos < topic_pos + + +class TestContextMemoryApply: + """Tests for ContextMemory.apply() method.""" + + def test_add_operation_with_topic_ids(self): + """ADD operation assigns topic_ids to new items.""" + memory = ContextMemory(topics=[Topic(id="t1", description="Test")]) + ops = [Operation(type=OpType.ADD, section="context_understanding", content="New item")] + new_memory, new_ids = memory.apply(ops, topic_ids=["t1"]) + + assert len(new_ids) == 1 + item = new_memory.context_understanding[0] + assert item.topic_ids == ["t1"] + + def test_replace_preserves_existing_topic_ids(self): + """REPLACE operation preserves existing topic_ids.""" + memory = ContextMemory( + context_understanding=[ + Item(id="cu-abc", content="Original", topic_ids=["t1"]), + ], + ) + ops = [Operation(type=OpType.REPLACE, item_id="cu-abc", content="Replaced")] + new_memory, _ = memory.apply(ops, topic_ids=["t2"]) + + item = new_memory.context_understanding[0] + assert item.content == "Replaced" + assert item.topic_ids == ["t1"] # Preserved, not overwritten + + def test_add_without_topic_ids(self): + """ADD without topic_ids creates item with empty topic_ids.""" + memory = ContextMemory() + ops = [Operation(type=OpType.ADD, section="context_understanding", content="New item")] + new_memory, _ = memory.apply(ops) + + item = new_memory.context_understanding[0] + assert item.topic_ids == [] + + def test_delete_removes_item(self): + """DELETE operation removes item.""" + memory = ContextMemory( + context_understanding=[ + Item(id="cu-abc", content="To delete", topic_ids=["t1"]), + ], + ) + ops = [Operation(type=OpType.DELETE, item_id="cu-abc")] + new_memory, _ = memory.apply(ops) + + assert len(new_memory.context_understanding) == 0 + + def test_replace_nonexistent_logs_warning(self, caplog): + """REPLACE on non-existent item logs warning and leaves memory unchanged.""" + import logging + + memory = ContextMemory( + context_understanding=[ + Item(id="cu-abc", content="Existing", topic_ids=["t1"]), + ], + ) + ops = [Operation(type=OpType.REPLACE, item_id="cu-GONE", content="New content")] + with caplog.at_level( + logging.WARNING, + logger="codespy.agents.memory.hippocampus.context_memory", + ): + new_memory, new_ids = memory.apply(ops, topic_ids=["t2"]) + + assert new_ids == [] + assert new_memory.context_understanding[0].content == "Existing" + assert "cu-GONE" in caplog.text + assert "not found" in caplog.text + + +class TestContextMemoryMerge: + """Tests for ContextMemory.merge() method.""" + + def test_merge_deduplicates_topics(self): + """Merge deduplicates topics by ID.""" + mem1 = ContextMemory(topics=[Topic(id="t1", description="First")]) + mem2 = ContextMemory(topics=[Topic(id="t1", description="Second")]) + merged = ContextMemory.merge(mem1, mem2) + + assert len(merged.topics) == 1 + + def test_merge_later_description_wins(self): + """Later non-empty description wins in topic merge.""" + mem1 = ContextMemory(topics=[Topic(id="t1", description="")]) + mem2 = ContextMemory(topics=[Topic(id="t1", description="Better description")]) + merged = ContextMemory.merge(mem1, mem2) + + assert merged.topics[0].description == "Better description" + + def test_merge_items_by_id(self): + """Merge replaces items with same ID (later wins).""" + mem1 = ContextMemory( + context_understanding=[Item(id="cu-abc", content="First", topic_ids=["t1"])], + ) + mem2 = ContextMemory( + context_understanding=[Item(id="cu-abc", content="Second", topic_ids=["t2"])], + ) + merged = ContextMemory.merge(mem1, mem2) + + assert len(merged.context_understanding) == 1 + assert merged.context_understanding[0].content == "Second" + + def test_merge_multiple_memories(self): + """Merge can handle multiple memories.""" + mem1 = ContextMemory( + topics=[Topic(id="t1", description="T1")], + context_understanding=[Item(id="cu-1", content="Item 1", topic_ids=["t1"])], + ) + mem2 = ContextMemory( + topics=[Topic(id="t2", description="T2")], + context_understanding=[Item(id="cu-2", content="Item 2", topic_ids=["t2"])], + ) + mem3 = ContextMemory( + topics=[Topic(id="t3", description="T3")], + domain_constants=[Item(id="dc-1", content="Constant", topic_ids=["t3"])], + ) + merged = ContextMemory.merge(mem1, mem2, mem3) + + assert len(merged.topics) == 3 + assert len(merged.context_understanding) == 2 + assert len(merged.domain_constants) == 1 + + +class TestTopicDependencies: + """Tests for Topic.dependencies field.""" + + def test_topic_with_dependencies(self): + """Topic can have dependencies.""" + topic = Topic( + id="owner/repo/auth", + description="Auth service", + dependencies=["owner/repo/core", "PyPI/passlib"] + ) + assert topic.dependencies == ["owner/repo/core", "PyPI/passlib"] + + def test_topic_dependencies_default_empty(self): + """Topic dependencies default to empty list.""" + topic = Topic(id="owner/repo/auth", description="Auth service") + assert topic.dependencies == [] + + def test_topic_deserialization_without_dependencies(self): + """Old episodes without dependencies deserialize to empty list.""" + old_data = '{"id": "owner/repo/auth", "description": "Auth service"}' + topic = Topic.model_validate_json(old_data) + assert topic.dependencies == [] + + +class TestScopeResultTopicHelper: + """Tests for ScopeResult.topic() helper method.""" + + def test_topic_with_package_manifest(self): + """Topic uses package_name from manifest when available.""" + from codespy.agents.reviewer.models import PackageManifest, ScopeResult, ScopeType + + scope = ScopeResult( + repo="owner/repo", + subroot="packages/auth", + scope_type=ScopeType.SERVICE, + reason="test", + description="Auth service", + package_manifest=PackageManifest( + manifest_path="packages/auth/package.json", + package_manager="npm", + package_name="@myorg/auth", + ), + ) + topic = scope.topic("owner/repo") + + assert topic.id == "owner/repo/@myorg/auth" + assert topic.description == "Auth service" + + def test_topic_without_package_manifest(self): + """Topic uses subroot when no package manifest.""" + from codespy.agents.reviewer.models import ScopeResult, ScopeType + + scope = ScopeResult( + repo="owner/repo", + subroot="services/api", + scope_type=ScopeType.SERVICE, + reason="test", + description="API service", + ) + topic = scope.topic("owner/repo") + + assert topic.id == "owner/repo/services/api" + assert topic.description == "API service" + + def test_topic_for_root_scope(self): + """Root scope topic uses just repo_full_name.""" + from codespy.agents.reviewer.models import ScopeResult, ScopeType + + scope = ScopeResult( + repo="owner/repo", + subroot=".", + scope_type=ScopeType.APPLICATION, + reason="test", + description="Repository root", + ) + topic = scope.topic("owner/repo") + + assert topic.id == "owner/repo" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/test_dspy_config.py b/tests/test_dspy_config.py new file mode 100644 index 0000000..cd7e556 --- /dev/null +++ b/tests/test_dspy_config.py @@ -0,0 +1,85 @@ +"""Tests for dspy_config model-capability helpers. + +These tests use a standalone copy of the _supports_cache_control function + to avoid heavy import dependencies on dspy, litellm, and other modules. +""" + +from unittest.mock import patch + + +# Standalone copy of the function for isolated testing +def _supports_cache_control(model: str) -> bool: + """Whether the model uses explicit Anthropic-style cache_control markers. + + Returns True only for models where the provider expects ``cache_control`` + fields in messages to enable prompt caching. Models with automatic caching + (OpenAI prefix-match, Gemini) or no caching return False — sending markers + to them is either pointless or causes provider errors. + + Detection: ``cache_creation_input_token_cost`` is non-None in LiteLLM's + model database only for Anthropic-style providers that charge separately + for cache writes, which correlates exactly with explicit marker support. + + Falls back to provider-prefix heuristic when model info is unavailable. + """ + import litellm # noqa: F401 - this is mocked + try: + info = litellm.get_model_info(model) + return info.get("cache_creation_input_token_cost") is not None + except Exception: + # Model not in LiteLLM DB (Ollama offline, custom endpoint). + # Fall back to prefix heuristic. + lower = model.lower() + if lower.startswith("anthropic/"): + return True + return bool(lower.startswith("bedrock/") and "anthropic" in lower) + + +class TestSupportsCacheControl: + """Tests for _supports_cache_control().""" + + def test_anthropic_direct_supported(self): + with patch("litellm.get_model_info") as mock: + mock.return_value = {"cache_creation_input_token_cost": 6.25e-06} + assert _supports_cache_control("anthropic/claude-opus-4-6") is True + + def test_bedrock_anthropic_supported(self): + with patch("litellm.get_model_info") as mock: + mock.return_value = {"cache_creation_input_token_cost": 3.75e-06} + assert _supports_cache_control("bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0") is True + + def test_openai_no_explicit_markers(self): + with patch("litellm.get_model_info") as mock: + mock.return_value = {"cache_creation_input_token_cost": None} + assert _supports_cache_control("openai/gpt-5") is False + + def test_gemini_no_explicit_markers(self): + with patch("litellm.get_model_info") as mock: + mock.return_value = {"cache_creation_input_token_cost": None} + assert _supports_cache_control("gemini/gemini-2.5-pro") is False + + def test_bedrock_non_anthropic_no_markers(self): + with patch("litellm.get_model_info") as mock: + mock.return_value = {"cache_creation_input_token_cost": None} + assert _supports_cache_control("bedrock/amazon.titan-text-express-v1") is False + + def test_fallback_anthropic_prefix_when_info_unavailable(self): + with patch("litellm.get_model_info", side_effect=Exception("not found")): + assert _supports_cache_control("anthropic/claude-unknown-model") is True + + def test_fallback_bedrock_anthropic_when_info_unavailable(self): + with patch("litellm.get_model_info", side_effect=Exception("not found")): + assert _supports_cache_control("bedrock/us.anthropic.claude-future-v1") is True + + def test_fallback_ollama_when_info_unavailable(self): + with patch("litellm.get_model_info", side_effect=Exception("not found")): + assert _supports_cache_control("ollama/llama-4-70b") is False + + def test_fallback_unknown_provider_returns_false(self): + with patch("litellm.get_model_info", side_effect=Exception("not found")): + assert _supports_cache_control("custom/my-model") is False + + def test_missing_key_in_info_dict_returns_false(self): + with patch("litellm.get_model_info") as mock: + mock.return_value = {} # Key not present at all + assert _supports_cache_control("some/model") is False diff --git a/tests/test_hippocampus.py b/tests/test_hippocampus.py new file mode 100644 index 0000000..fad437e --- /dev/null +++ b/tests/test_hippocampus.py @@ -0,0 +1,135 @@ +"""Tests for Hippocampus internal methods.""" + +import pytest + +from codespy.agents.memory.hippocampus import ( + ContextMemory, + Hippocampus, + Item, + ItemTag, + Operation, + OpType, +) + + +class TestRecordMutations: + """Unit tests for _record_mutations back-fill logic.""" + + @staticmethod + def _make_hip(topic_ids=None, distill_step=0): + """Create minimal Hippocampus bypassing __init__ for unit testing.""" + hip = object.__new__(Hippocampus) + hip._distill_step = distill_step + hip._topic_ids = topic_ids or ["t1"] + return hip + + def test_add_backfill_with_mixed_ops(self): + """ADD item_ids back-filled correctly when DELETEs precede them.""" + pre = ContextMemory( + context_understanding=[Item(id="cu-existing", content="Old", topic_ids=["t1"])], + ) + ops = [ + Operation(type=OpType.DELETE, item_id="cu-existing"), + Operation(type=OpType.ADD, section="context_understanding", content="New 1"), + Operation(type=OpType.ADD, section="domain_constants", content="New 2"), + ] + new_ids = ["cu-aaa", "dc-bbb"] + + hip = self._make_hip() + mutations = hip._record_mutations(ops, new_ids, pre) + + assert len(mutations) == 3 + assert mutations[0].type == OpType.DELETE + assert mutations[0].item_id == "cu-existing" + assert mutations[1].type == OpType.ADD + assert mutations[1].item_id == "cu-aaa" + assert mutations[2].type == OpType.ADD + assert mutations[2].item_id == "dc-bbb" + + def test_add_backfill_skipped_delete(self): + """ADD correct even when DELETE target not found (no mutation emitted).""" + pre = ContextMemory() # empty — DELETE won't find anything + ops = [ + Operation(type=OpType.DELETE, item_id="cu-ghost"), + Operation(type=OpType.ADD, section="context_understanding", content="New"), + ] + new_ids = ["cu-xyz"] + + hip = self._make_hip() + mutations = hip._record_mutations(ops, new_ids, pre) + + assert len(mutations) == 1 + assert mutations[0].type == OpType.ADD + assert mutations[0].item_id == "cu-xyz" + + def test_add_backfill_all_adds(self): + """All-ADD batch back-fills in order.""" + pre = ContextMemory() + ops = [ + Operation(type=OpType.ADD, section="context_understanding", content="A"), + Operation(type=OpType.ADD, section="domain_constants", content="B"), + Operation(type=OpType.ADD, section="reusable_results", content="C"), + ] + new_ids = ["cu-1", "dc-2", "rr-3"] + + hip = self._make_hip() + mutations = hip._record_mutations(ops, new_ids, pre) + + assert [m.item_id for m in mutations] == ["cu-1", "dc-2", "rr-3"] + assert all(m.type == OpType.ADD for m in mutations) + + def test_add_backfill_length_mismatch_raises(self): + """zip(strict=True) raises ValueError on length mismatch.""" + pre = ContextMemory() + ops = [ + Operation(type=OpType.ADD, section="context_understanding", content="A"), + ] + new_ids = ["cu-1", "cu-2"] # too many IDs + + hip = self._make_hip() + with pytest.raises(ValueError): + hip._record_mutations(ops, new_ids, pre) + + +class TestUpdateItemScores: + """Unit tests for _update_item_scores scoring logic.""" + + @staticmethod + def _make_hip(): + """Create minimal Hippocampus with empty scores.""" + hip = object.__new__(Hippocampus) + hip.scores = {} + return hip + + def test_helpful_increments(self): + hip = self._make_hip() + hip._update_item_scores({"a": ItemTag.HELPFUL}) + assert hip.scores == {"a": 1} + + def test_helpful_accumulates(self): + hip = self._make_hip() + hip.scores = {"a": 3} + hip._update_item_scores({"a": ItemTag.HELPFUL}) + assert hip.scores == {"a": 4} + + def test_harmful_decrements(self): + hip = self._make_hip() + hip.scores = {"a": 2} + hip._update_item_scores({"a": ItemTag.HARMFUL}) + assert hip.scores == {"a": 1} + + def test_stale_decrements(self): + hip = self._make_hip() + hip._update_item_scores({"a": ItemTag.STALE}) + assert hip.scores == {"a": -1} + + def test_neutral_initializes_zero(self): + hip = self._make_hip() + hip._update_item_scores({"a": ItemTag.NEUTRAL}) + assert hip.scores == {"a": 0} + + def test_neutral_preserves_existing(self): + hip = self._make_hip() + hip.scores = {"a": 5} + hip._update_item_scores({"a": ItemTag.NEUTRAL}) + assert hip.scores == {"a": 5} diff --git a/tests/test_patch_utils.py b/tests/test_patch_utils.py new file mode 100644 index 0000000..58c9e63 --- /dev/null +++ b/tests/test_patch_utils.py @@ -0,0 +1,590 @@ +"""Tests for patch_utils module.""" + +from pathlib import Path +from unittest.mock import MagicMock + +from codespy.agents.reviewer.models import ScopeResult, ScopeType +from codespy.tools.git.models import ChangedFile, FileStatus +from codespy.tools.git.patch_utils import ( + _expand_hunk_to_functions, + _merge_hunks, + _parse_hunks, + _rebuild_patch, + _should_compact_file, + compact_patch, + compact_patches, +) +from codespy.tools.parsers.treesitter import FunctionInfo + + +class TestShouldCompactFile: + """Tests for _should_compact_file function.""" + + def test_returns_true_for_code_file_with_patch(self): + file = ChangedFile( + filename="src/main.py", + status=FileStatus.MODIFIED, + patch="@@ -1,3 +1,3 @@\n-old\n+new", + ) + assert _should_compact_file(file) is True + + def test_returns_false_for_no_patch(self): + file = ChangedFile( + filename="src/main.py", + status=FileStatus.MODIFIED, + patch=None, + ) + assert _should_compact_file(file) is False + + def test_returns_false_for_deleted_file(self): + file = ChangedFile( + filename="src/main.py", + status=FileStatus.REMOVED, + patch="@@ -1,3 +0,0 @@\n-old", + ) + assert _should_compact_file(file) is False + + def test_returns_false_for_non_code_file(self): + file = ChangedFile( + filename="README.md", + status=FileStatus.MODIFIED, + patch="@@ -1,3 +1,3 @@\n-old\n+new", + ) + assert _should_compact_file(file) is False + + def test_returns_false_for_binary_file(self): + file = ChangedFile( + filename="image.png", + status=FileStatus.ADDED, + patch=None, + ) + assert _should_compact_file(file) is False + + def test_returns_false_for_lock_file(self): + file = ChangedFile( + filename="package-lock.json", + status=FileStatus.MODIFIED, + patch="@@ -1,3 +1,3 @@\n-old\n+new", + ) + assert _should_compact_file(file) is False + + +class TestParseHunks: + """Tests for _parse_hunks function.""" + + def test_parses_single_hunk(self): + patch = """@@ -1,5 +1,5 @@ + def hello(): +- print("old") ++ print("new") + pass""" + hunks = _parse_hunks(patch) + + assert len(hunks) == 1 + assert hunks[0]["old_start"] == 1 + assert hunks[0]["old_count"] == 5 + assert hunks[0]["new_start"] == 1 + assert hunks[0]["new_count"] == 5 + assert len(hunks[0]["lines"]) == 4 # Includes the context line + + def test_parses_multiple_hunks(self): + patch = """@@ -1,3 +1,3 @@ + line1 +-line2 ++line2_changed + line3 +@@ -10,3 +10,3 @@ + line10 +-line11 ++line11_changed + line12""" + hunks = _parse_hunks(patch) + + assert len(hunks) == 2 + assert hunks[0]["new_start"] == 1 + assert hunks[1]["new_start"] == 10 + + def test_handles_hunk_without_old_count(self): + patch = """@@ -1 +1 @@ +-old ++new""" + hunks = _parse_hunks(patch) + + assert len(hunks) == 1 + assert hunks[0]["old_count"] == 1 + assert hunks[0]["new_count"] == 1 + + def test_tracks_changed_new_lines(self): + patch = """@@ -5,5 +5,6 @@ + context1 +-added1 + context2 ++added2 + context3""" + hunks = _parse_hunks(patch) + + # Changed lines in new file: line 7 (added2) - deleted lines aren't in new file + # Line 5: context1 (line 5), Line 6: deleted (not in new), Line 6: context2 (line 6) + # Line 7: added2 (line 7) + assert hunks[0]["changed_new_lines"] == [7] + + def test_returns_empty_list_for_no_hunks(self): + patch = "no hunk headers here" + hunks = _parse_hunks(patch) + assert hunks == [] + + def test_handles_no_newline_at_end_marker(self): + patch = """@@ -1,3 +1,3 @@ + line1 + line2 +-line3 ++line3_new +\\ No newline at end of file""" + hunks = _parse_hunks(patch) + + assert len(hunks) == 1 + # The "\ No newline" line is included in hunk lines + # Lines: line1 (context), -line3 (deleted), +line3_new (added), \ marker + assert len(hunks[0]["lines"]) == 5 + + +class TestExpandHunkToFunctions: + """Tests for _expand_hunk_to_functions function.""" + + def test_expands_to_enclosing_function(self): + hunk = { + "header": "@@ -17,7 +17,7 @@", + "old_start": 17, + "old_count": 7, + "new_start": 17, + "new_count": 7, + "lines": [" line1", "- old_line", "+ new_line", " line2"], + "changed_new_lines": [18], + } + # Function from line 10 to 30 + functions = [FunctionInfo( + name="test_func", + file="test.py", + line_start=10, + line_end=30, + parameters=[], + )] + source_lines = [f"line {i}" for i in range(1, 35)] + + result = _expand_hunk_to_functions(hunk, functions, source_lines) + + assert result is not None + assert result["expansion_start"] == 10 + assert result["expansion_end"] == 30 + + def test_no_expansion_for_change_outside_function(self): + hunk = { + "header": "@@ -1,3 +1,4 @@", + "old_start": 1, + "old_count": 3, + "new_start": 1, + "new_count": 4, + "lines": ["+import new_module", " import os"], + "changed_new_lines": [1], + } + # Function starts at line 10 + functions = [FunctionInfo( + name="test_func", + file="test.py", + line_start=10, + line_end=30, + parameters=[], + )] + source_lines = [f"line {i}" for i in range(1, 35)] + + result = _expand_hunk_to_functions(hunk, functions, source_lines) + + # Should return hunk unchanged (no enclosing function) + assert result == hunk + + def test_uses_innermost_function_for_nested(self): + hunk = { + "header": "@@ -15,3 +15,3 @@", + "old_start": 15, + "old_count": 3, + "new_start": 15, + "new_count": 3, + "lines": ["- old", "+ new", " pass"], + "changed_new_lines": [15], + } + # Outer function: lines 5-25, inner function: lines 12-18 + functions = [ + FunctionInfo(name="outer", file="test.py", line_start=5, line_end=25, parameters=[]), + FunctionInfo(name="inner", file="test.py", line_start=12, line_end=18, parameters=[]), + ] + source_lines = [f"line {i}" for i in range(1, 30)] + + result = _expand_hunk_to_functions(hunk, functions, source_lines) + + # Should use inner function boundaries + assert result["expansion_start"] == 12 + assert result["expansion_end"] == 18 + + def test_expands_multiple_functions_in_one_hunk(self): + hunk = { + "header": "@@ -20,10 +20,12 @@", + "old_start": 20, + "old_count": 10, + "new_start": 20, + "new_count": 12, + "lines": ["-old", "+new"], + "changed_new_lines": [22, 35], + } + functions = [ + FunctionInfo(name="func1", file="test.py", line_start=15, line_end=25, parameters=[]), + FunctionInfo(name="func2", file="test.py", line_start=30, line_end=40, parameters=[]), + ] + source_lines = [f"line {i}" for i in range(1, 50)] + + result = _expand_hunk_to_functions(hunk, functions, source_lines) + + # Should expand to cover both functions + assert result["expansion_start"] == 15 + assert result["expansion_end"] == 40 + + +class TestMergeHunks: + """Tests for _merge_hunks function.""" + + def test_returns_single_hunk_unchanged(self): + hunks = [{"expansion_start": 10, "expansion_end": 30}] + merged = _merge_hunks(hunks) + assert merged == hunks + + def test_merges_overlapping_hunks(self): + hunks = [ + {"expansion_start": 10, "expansion_end": 30, "original_hunk": {"header": "@@ -17,7 +17,7 @@"}}, + {"expansion_start": 25, "expansion_end": 50, "original_hunk": {"header": "@@ -40,5 +40,5 @@"}}, + ] + merged = _merge_hunks(hunks) + + assert len(merged) == 1 + assert merged[0]["expansion_start"] == 10 + assert merged[0]["expansion_end"] == 50 + + def test_merges_adjacent_hunks(self): + hunks = [ + {"expansion_start": 10, "expansion_end": 20}, + {"expansion_start": 21, "expansion_end": 30}, # Adjacent (gap = 1) + ] + merged = _merge_hunks(hunks) + + assert len(merged) == 1 + assert merged[0]["expansion_start"] == 10 + assert merged[0]["expansion_end"] == 30 + + def test_keeps_separate_non_overlapping_hunks(self): + hunks = [ + {"expansion_start": 10, "expansion_end": 20}, + {"expansion_start": 30, "expansion_end": 40}, # Gap of 9 lines + ] + merged = _merge_hunks(hunks) + + assert len(merged) == 2 + + def test_sorts_hunks_by_start(self): + hunks = [ + {"expansion_start": 50, "expansion_end": 60}, + {"expansion_start": 10, "expansion_end": 20}, + ] + merged = _merge_hunks(hunks) + + assert merged[0]["expansion_start"] == 10 + assert merged[1]["expansion_start"] == 50 + + +class TestRebuildPatch: + """Tests for _rebuild_patch function.""" + + def test_rebuilds_single_expanded_hunk(self): + raw_patch = """@@ -17,7 +17,7 @@ def generate_token(user_id: str, ttl_hours: int = 24) -> str: + expiry = datetime.utcnow() + timedelta(hours=ttl_hours) + payload = f"{user_id}:{expiry.isoformat()}" + signature = hashlib.sha256(f"{payload}{SECRET_KEY}".encode()).hexdigest() +- return f"{payload}:{signature}" ++ return f"{payload}.{signature}" + + + def verify_token(token: str) -> bool:""" + + source_lines = [f"line {i}" for i in range(1, 50)] + merged_hunks = [ + { + "original_hunk": { + "header": "@@ -17,7 +17,7 @@", + "lines": [ + " expiry = datetime.utcnow() + timedelta(hours=ttl_hours)", + ' payload = f"{user_id}:{expiry.isoformat()}"', + ' signature = hashlib.sha256(f"{payload}{SECRET_KEY}".encode()).hexdigest()', + '- return f"{payload}:{signature}"', + '+ return f"{payload}.{signature}"', + "", + "", + " def verify_token(token: str) -> bool:", + ], + }, + "expansion_start": 10, + "expansion_end": 30, + "hunk_start_new": 17, + "hunk_end_new": 24, + } + ] + + result = _rebuild_patch(raw_patch, merged_hunks, source_lines) + + assert result is not None + assert "@@ -10," in result # New header starts at expansion_start + + def test_keeps_header_lines(self): + raw_patch = """diff --git a/src/main.py b/src/main.py +index 123..456 789 +--- a/src/main.py ++++ b/src/main.py +@@ -1,3 +1,3 @@ +-old ++new""" + + source_lines = ["line1", "line2", "line3"] + merged_hunks = [ + { + "original_hunk": { + "header": "@@ -1,3 +1,3 @@", + "lines": ["-old", "+new"], + }, + "expansion_start": 1, + "expansion_end": 3, + "hunk_start_new": 1, + "hunk_end_new": 2, + } + ] + + result = _rebuild_patch(raw_patch, merged_hunks, source_lines) + + assert "diff --git" in result + assert "index 123..456" in result + + def test_returns_none_for_empty_hunks(self): + result = _rebuild_patch("patch", [], []) + assert result is None + + +class TestCompactPatch: + """Tests for compact_patch function.""" + + def test_returns_original_for_empty_patch(self): + result = compact_patch("", Path("test.py"), Path("/repo")) + assert result == "" + + def test_returns_original_when_no_functions_found(self, tmp_path: Path): + # Create a Python file without functions + source_file = tmp_path / "test.py" + source_file.write_text("x = 1\ny = 2\n") + + patch = """@@ -1,2 +1,2 @@ +-x = 1 ++x = 2 + y = 2""" + + mock_parser = MagicMock() + mock_parser.available = True + mock_parser.find_function_definitions.return_value = [] + + result = compact_patch(patch, Path("test.py"), tmp_path, mock_parser) + assert result == patch + + def test_returns_original_when_treesitter_unavailable(self, tmp_path: Path): + patch = """@@ -1,3 +1,3 @@ +-old ++new""" + + mock_parser = MagicMock() + mock_parser.available = False + + result = compact_patch(patch, Path("test.py"), tmp_path, mock_parser) + assert result == patch + + def test_returns_original_for_non_code_file(self, tmp_path: Path): + patch = """@@ -1,3 +1,3 @@ +-old ++new""" + + result = compact_patch(patch, Path("README.md"), tmp_path) + assert result == patch + + +class TestCompactPatches: + """Tests for compact_patches function.""" + + def test_processes_multiple_scopes(self, tmp_path: Path, monkeypatch): + # Create source files + (tmp_path / "src").mkdir() + (tmp_path / "src" / "main.py").write_text("x = 1\n") + (tmp_path / "src" / "utils.py").write_text("y = 2\n") + + scope1 = ScopeResult( + repo="owner/repo", + subroot="pkg1", + scope_type=ScopeType.LIBRARY, + reason="test", + changed_files=[ + ChangedFile( + filename="src/main.py", + status=FileStatus.MODIFIED, + patch="@@ -1,3 +1,3 @@\n-old\n+new", + ) + ], + ) + scope2 = ScopeResult( + repo="owner/repo", + subroot="pkg2", + scope_type=ScopeType.SERVICE, + reason="test", + changed_files=[ + ChangedFile( + filename="src/utils.py", + status=FileStatus.MODIFIED, + patch="@@ -5,3 +5,3 @@\n-old\n+new", + ) + ], + ) + scopes = [scope1, scope2] + + # Mock TreeSitterParser to return empty functions + call_count = 0 + def mock_find_functions(self, file_path): + nonlocal call_count + call_count += 1 + return [] + + monkeypatch.setattr( + "codespy.tools.git.patch_utils.TreeSitterParser.find_function_definitions", + mock_find_functions, + ) + monkeypatch.setattr( + "codespy.tools.git.patch_utils.TreeSitterParser.__init__", lambda self, path: None + ) + monkeypatch.setattr( + "codespy.tools.git.patch_utils.TreeSitterParser.available", True + ) + + compact_patches(scopes, tmp_path) + + # Should have tried to find functions in both files + assert call_count == 2 + + def test_skips_deleted_files(self, tmp_path: Path, monkeypatch): + scope = ScopeResult( + repo="owner/repo", + subroot=".", + scope_type=ScopeType.LIBRARY, + reason="test", + changed_files=[ + ChangedFile( + filename="src/deleted.py", + status=FileStatus.REMOVED, + patch="@@ -1,3 +0,0 @@\n-line1\n-line2\n-line3", + ) + ], + ) + + call_count = 0 + def mock_find_functions(self, file_path): + nonlocal call_count + call_count += 1 + return [] + + monkeypatch.setattr( + "codespy.tools.git.patch_utils.TreeSitterParser.find_function_definitions", + mock_find_functions, + ) + monkeypatch.setattr( + "codespy.tools.git.patch_utils.TreeSitterParser.__init__", lambda self, path: None + ) + monkeypatch.setattr( + "codespy.tools.git.patch_utils.TreeSitterParser.available", True + ) + + compact_patches([scope], tmp_path) + + # Should not try to find functions in deleted files + assert call_count == 0 + + def test_skips_files_without_patches(self, tmp_path: Path, monkeypatch): + scope = ScopeResult( + repo="owner/repo", + subroot=".", + scope_type=ScopeType.LIBRARY, + reason="test", + changed_files=[ + ChangedFile( + filename="src/empty.py", + status=FileStatus.MODIFIED, + patch=None, + ) + ], + ) + + call_count = 0 + def mock_find_functions(self, file_path): + nonlocal call_count + call_count += 1 + return [] + + monkeypatch.setattr( + "codespy.tools.git.patch_utils.TreeSitterParser.find_function_definitions", + mock_find_functions, + ) + monkeypatch.setattr( + "codespy.tools.git.patch_utils.TreeSitterParser.__init__", lambda self, path: None + ) + monkeypatch.setattr( + "codespy.tools.git.patch_utils.TreeSitterParser.available", True + ) + + compact_patches([scope], tmp_path) + + assert call_count == 0 + + def test_handles_compaction_failure_gracefully(self, tmp_path: Path, monkeypatch): + # Create source file + source_file = tmp_path / "test.py" + source_file.write_text("def func():\n pass\n") + + scope = ScopeResult( + repo="owner/repo", + subroot=".", + scope_type=ScopeType.LIBRARY, + reason="test", + changed_files=[ + ChangedFile( + filename="test.py", + status=FileStatus.MODIFIED, + patch="@@ -1,3 +1,3 @@\n-old\n+new", + ) + ], + ) + + def mock_find_functions(self, file_path): + raise Exception("parse error") + + monkeypatch.setattr( + "codespy.tools.git.patch_utils.TreeSitterParser.find_function_definitions", + mock_find_functions, + ) + monkeypatch.setattr( + "codespy.tools.git.patch_utils.TreeSitterParser.__init__", lambda self, path: None + ) + monkeypatch.setattr( + "codespy.tools.git.patch_utils.TreeSitterParser.available", True + ) + + # Should not raise + compact_patches([scope], tmp_path) + + # Original patch should be preserved + assert scope.changed_files[0].patch == "@@ -1,3 +1,3 @@\n-old\n+new" diff --git a/tests/test_s3_client.py b/tests/test_s3_client.py new file mode 100644 index 0000000..d05cd4e --- /dev/null +++ b/tests/test_s3_client.py @@ -0,0 +1,110 @@ +import pytest + +from codespy.tools.storage.s3.client import S3Client + + +class TestResolvePath: + def setup_method(self): + # Patch boto3 — we only test path logic + self.client = S3Client.__new__(S3Client) + + def test_normal_path(self): + assert self.client._resolve_path("foo/bar/baz.txt") == "foo/bar/baz.txt" + + def test_strips_leading_slash(self): + assert self.client._resolve_path("/foo/bar") == "foo/bar" + + def test_collapses_double_slash(self): + assert self.client._resolve_path("foo//bar") == "foo/bar" + + def test_resolves_dot(self): + assert self.client._resolve_path("foo/./bar") == "foo/bar" + + def test_rejects_traversal(self): + with pytest.raises(ValueError, match="escapes bucket root"): + self.client._resolve_path("foo/../../etc/passwd") + + def test_rejects_leading_traversal(self): + with pytest.raises(ValueError, match="escapes bucket root"): + self.client._resolve_path("../secret") + + def test_rejects_interior_traversal(self): + """Paths with '..' anywhere are now rejected, not just those escaping root.""" + with pytest.raises(ValueError, match="escapes bucket root"): + self.client._resolve_path("a/b/../c") + + def test_empty_after_normalization(self): + assert self.client._resolve_path(".") == "" + assert self.client._resolve_path("/") == "" + + def test_rejects_encoded_traversal_lowercase(self): + """Percent-encoded '..' (%2e%2e) must be caught.""" + with pytest.raises(ValueError, match="escapes bucket root"): + self.client._resolve_path("foo/%2e%2e/secret") + + def test_rejects_encoded_traversal_uppercase(self): + """Mixed/uppercase percent-encoding must also be caught.""" + with pytest.raises(ValueError, match="escapes bucket root"): + self.client._resolve_path("foo/%2E%2E/secret") + + def test_rejects_leading_encoded_traversal(self): + with pytest.raises(ValueError, match="escapes bucket root"): + self.client._resolve_path("%2e%2e/etc/passwd") + + def test_rejects_encoded_slash_traversal(self): + """Encoded forward slash creating '..' component when decoded.""" + with pytest.raises(ValueError, match="escapes bucket root"): + self.client._resolve_path("foo%2f..%2fsecret") + + def test_preserves_percent_in_normal_keys(self): + """Legitimate keys with percent characters pass through unchanged.""" + assert self.client._resolve_path("reports/100%25done.txt") == "reports/100%25done.txt" + + def test_encoded_nontraversal_preserved(self): + """Non-traversal encoded chars: original key preserved in return value.""" + assert self.client._resolve_path("foo/%62ar") == "foo/%62ar" + + def test_double_encoded_traversal_passes(self): + """Double-encoded traversal is an opaque S3 key, not actual traversal.""" + # %252e%252e decodes once to %2e%2e (not ..) — legitimate key + assert self.client._resolve_path("foo/%252e%252e/bar") == "foo/%252e%252e/bar" + + +class TestReadFileTruncation: + @staticmethod + def _truncate_utf8(raw: bytes, max_bytes: int) -> bytes: + """Reproduce the fixed truncation logic.""" + if len(raw) <= max_bytes: + return raw + raw = raw[:max_bytes] + try: + raw.decode("utf-8") + except UnicodeDecodeError as e: + if e.start >= len(raw) - 4: + raw = raw[:e.start] + return raw + + def test_truncate_preserves_utf8(self): + # "café" = 63 61 66 c3 a9 (5 bytes), max_bytes=4 cuts inside é + raw = "café".encode() + result = self._truncate_utf8(raw, 4).decode("utf-8") + assert result == "caf" + + def test_truncate_emoji(self): + raw = "hi🎉bye".encode() # 9 bytes + result = self._truncate_utf8(raw, 4).decode("utf-8") + assert result == "hi" + + def test_truncate_at_exact_boundary_preserves_character(self): + """Bug regression: truncation at valid char boundary must NOT strip it.""" + # "àè" = c3 a0 c3 a8 (4 bytes), max_bytes=4 lands exactly at end of è + raw = "àè".encode() + result = self._truncate_utf8(raw, 4).decode("utf-8") + assert result == "àè" # Both characters preserved (was bug: stripped è) + + def test_truncate_between_two_multibyte(self): + """Truncation between two multi-byte characters preserves the first.""" + # "àè" = c3 a0 c3 a8, max_bytes=3 cuts inside è + raw = "àè".encode() + result = self._truncate_utf8(raw, 3).decode("utf-8") + assert result == "à" # è is incomplete, stripped diff --git a/tests/test_scope_resolver.py b/tests/test_scope_resolver.py new file mode 100644 index 0000000..49d8108 --- /dev/null +++ b/tests/test_scope_resolver.py @@ -0,0 +1,320 @@ +"""Tests for scope_resolver module.""" + +import tempfile +from pathlib import Path + +import pytest + +from codespy.agents.reviewer.models import ScopeType +from codespy.agents.reviewer.modules.scope_resolver import ( + ScopeResolver, + derive_sparse_paths, +) +from codespy.tools.git.models import ChangedFile, FileStatus + + +class TestDeriveSparsePaths: + """Test sparse path derivation.""" + + def test_single_scope_indicator(self): + """Test deriving sparse paths with single scope indicator.""" + changed_files = ["packages/auth/src/index.ts"] + paths = derive_sparse_paths(changed_files) + assert "packages/auth/" in paths + assert "/*" in paths + + def test_multiple_scope_indicators(self): + """Test deriving sparse paths with multiple indicators.""" + changed_files = [ + "mono/svc/api/cmd/main.go", + "packages/auth/src/index.ts", + ] + paths = derive_sparse_paths(changed_files) + assert "mono/svc/api/" in paths + assert "packages/auth/" in paths + assert "/*" in paths + + def test_no_scope_indicator_uses_depth_fallback(self): + """Test fallback to depth-2 when no indicator found.""" + changed_files = ["backend/api/handler.go"] + paths = derive_sparse_paths(changed_files) + assert "backend/api/" in paths + + def test_root_level_file(self): + """Test root-level files don't add extra paths.""" + changed_files = ["README.md", ".github/workflows/ci.yml"] + paths = derive_sparse_paths(changed_files) + # Should still have /* for root manifests + assert "/*" in paths + + def test_deduplication(self): + """Test that duplicate scope roots are deduplicated.""" + changed_files = [ + "packages/auth/src/index.ts", + "packages/auth/src/utils.ts", + "packages/auth/tests/auth.test.ts", + ] + paths = derive_sparse_paths(changed_files) + # Should only have one packages/auth/ + assert paths.count("packages/auth/") == 1 + + +class TestScopeResolver: + """Test ScopeResolver class.""" + + def test_discover_manifests(self): + """Test manifest discovery.""" + with tempfile.TemporaryDirectory() as tmpdir: + # Create a fake repo structure + repo_path = Path(tmpdir) + (repo_path / "packages" / "auth").mkdir(parents=True) + (repo_path / "packages" / "auth" / "package.json").touch() + (repo_path / "services" / "api").mkdir(parents=True) + (repo_path / "services" / "api" / "go.mod").touch() + + resolver = ScopeResolver() + manifests = resolver._discover_manifests(repo_path, []) + + assert len(manifests) == 2 + assert Path("packages/auth") in manifests + assert Path("services/api") in manifests + + def test_single_scope_repo(self): + """Test single-scope repo with root manifest.""" + with tempfile.TemporaryDirectory() as tmpdir: + repo_path = Path(tmpdir) + (repo_path / "go.mod").touch() + (repo_path / "main.go").touch() + + changed_files = [ + ChangedFile(filename="main.go", status=FileStatus.MODIFIED), + ] + + resolver = ScopeResolver() + scopes, orphans = resolver._resolve(repo_path, changed_files, "owner/repo") + + assert len(scopes) == 1 + assert scopes[0].subroot == "." + assert len(orphans) == 0 + + def test_mono_repo_with_packages(self): + """Test mono-repo with packages/ directory.""" + with tempfile.TemporaryDirectory() as tmpdir: + repo_path = Path(tmpdir) + (repo_path / "packages" / "auth" / "src").mkdir(parents=True) + (repo_path / "packages" / "auth" / "package.json").touch() + (repo_path / "packages" / "utils").mkdir(parents=True) + (repo_path / "packages" / "utils" / "package.json").touch() + + changed_files = [ + ChangedFile( + filename="packages/auth/src/index.ts", status=FileStatus.MODIFIED + ), + ] + + resolver = ScopeResolver() + scopes, orphans = resolver._resolve(repo_path, changed_files, "owner/repo") + + assert len(scopes) == 1 + assert scopes[0].subroot == "packages/auth" + + def test_orphan_file_no_manifest(self): + """Test file with no manifest becomes orphan.""" + with tempfile.TemporaryDirectory() as tmpdir: + repo_path = Path(tmpdir) + # No manifest files + + changed_files = [ + ChangedFile(filename="random/file.py", status=FileStatus.MODIFIED), + ] + + resolver = ScopeResolver() + scopes, orphans = resolver._resolve(repo_path, changed_files, "owner/repo") + + # Should have orphan since no manifest and no scope indicator + assert len(orphans) == 1 + + def test_lock_file_detection(self): + """Test that lock file changes are detected.""" + with tempfile.TemporaryDirectory() as tmpdir: + repo_path = Path(tmpdir) + (repo_path / "package.json").touch() + (repo_path / "package-lock.json").touch() + + changed_files = [ + ChangedFile( + filename="package-lock.json", status=FileStatus.MODIFIED + ), + ] + + resolver = ScopeResolver() + scopes, orphans = resolver._resolve(repo_path, changed_files, "owner/repo") + + assert len(scopes) == 1 + assert scopes[0].package_manifest is not None + assert scopes[0].package_manifest.dependencies_changed is True + + def test_scope_type_classification(self): + """Test scope type classification from path indicators.""" + with tempfile.TemporaryDirectory() as tmpdir: + repo_path = Path(tmpdir) + (repo_path / "services" / "api").mkdir(parents=True) + (repo_path / "services" / "api" / "go.mod").touch() + (repo_path / "libs" / "utils").mkdir(parents=True) + (repo_path / "libs" / "utils" / "go.mod").touch() + (repo_path / "apps" / "web").mkdir(parents=True) + (repo_path / "apps" / "web" / "package.json").touch() + + changed_files = [ + ChangedFile( + filename="services/api/main.go", status=FileStatus.MODIFIED + ), + ChangedFile( + filename="libs/utils/helpers.go", status=FileStatus.MODIFIED + ), + ChangedFile( + filename="apps/web/index.ts", status=FileStatus.MODIFIED + ), + ] + + resolver = ScopeResolver() + scopes, orphans = resolver._resolve(repo_path, changed_files, "owner/repo") + + by_subroot = {s.subroot: s for s in scopes} + assert by_subroot["services/api"].scope_type == ScopeType.SERVICE + assert by_subroot["libs/utils"].scope_type == ScopeType.LIBRARY + assert by_subroot["apps/web"].scope_type == ScopeType.APPLICATION + + +class TestManifestScoping: + """Test scope detection based on manifests.""" + + def test_manifest_creates_scope(self): + """Test manifests create proper scopes.""" + with tempfile.TemporaryDirectory() as tmpdir: + repo_path = Path(tmpdir) + (repo_path / "go.mod").touch() + + changed_files = [ + ChangedFile(filename="main.go", status=FileStatus.MODIFIED), + ] + + resolver = ScopeResolver() + scopes, orphans = resolver._resolve(repo_path, changed_files, "owner/repo") + + # Candidate should exist with manifest + assert scopes[0].package_manifest is not None + + + def test_internal_dir_not_scope_indicator(self): + """internal/ is not a scope indicator for sparse paths.""" + changed_files = ["backend/internal/cache/redis.go"] + paths = derive_sparse_paths(changed_files) + # Should use depth-2 fallback, not scope indicator + assert "backend/internal/" in paths # depth-2 prefix + + def test_indicator_suppressed_under_manifest(self): + """Indicator scopes not created when parent manifest covers file.""" + with tempfile.TemporaryDirectory() as tmpdir: + repo_path = Path(tmpdir) + (repo_path / "services" / "api").mkdir(parents=True) + (repo_path / "services" / "api" / "go.mod").touch() + + changed_files = [ + ChangedFile(filename="services/api/cmd/main.go", status=FileStatus.MODIFIED), + ] + + resolver = ScopeResolver() + scopes, orphans = resolver._resolve(repo_path, changed_files, "owner/repo") + + # Should be 1 scope (from manifest), not 2 (manifest + cmd/ indicator) + assert len(scopes) == 1 + assert scopes[0].subroot == "services/api" + assert len(orphans) == 0 + + def test_root_manifest_suppresses_indicators_when_sole_manifest(self): + """Root package.json as sole manifest suppresses indicator scopes.""" + with tempfile.TemporaryDirectory() as tmpdir: + repo_path = Path(tmpdir) + (repo_path / "package.json").touch() + (repo_path / "scripts" / "deploy").mkdir(parents=True) + + changed_files = [ + ChangedFile(filename="scripts/deploy/prod.sh", status=FileStatus.MODIFIED), + ] + + resolver = ScopeResolver() + scopes, orphans = resolver._resolve(repo_path, changed_files, "owner/repo") + + # scripts/ indicator is suppressed when root is the only manifest (single-package repo) + [s.subroot for s in scopes] + assert len(scopes) == 1 + assert scopes[0].subroot == "." + + def test_root_manifest_suppresses_when_sole_manifest(self): + """Single-package repo: root manifest suppresses all indicators.""" + with tempfile.TemporaryDirectory() as tmpdir: + repo_path = Path(tmpdir) + (repo_path / "pyproject.toml").touch() + (repo_path / "src" / "pkg" / "tools" / "git").mkdir(parents=True) + + changed_files = [ + ChangedFile(filename="src/pkg/tools/git/client.py", status=FileStatus.MODIFIED), + ] + + resolver = ScopeResolver() + scopes, orphans = resolver._resolve(repo_path, changed_files, "owner/repo") + + assert len(scopes) == 1 + assert scopes[0].subroot == "." + assert len(orphans) == 0 + + def test_root_does_not_suppress_when_nested_manifests_exist(self): + """Monorepo: root is container, indicators still fire for uncovered files.""" + with tempfile.TemporaryDirectory() as tmpdir: + repo_path = Path(tmpdir) + (repo_path / "package.json").touch() + (repo_path / "packages" / "auth").mkdir(parents=True) + (repo_path / "packages" / "auth" / "package.json").touch() + (repo_path / "scripts" / "deploy").mkdir(parents=True) + + changed_files = [ + ChangedFile(filename="scripts/deploy/prod.sh", status=FileStatus.MODIFIED), + ] + + resolver = ScopeResolver() + scopes, orphans = resolver._resolve(repo_path, changed_files, "owner/repo") + + # scripts/deploy indicator should fire — root has nested manifests + scope_subroots = [s.subroot for s in scopes] + assert "scripts/deploy" in scope_subroots + + +class TestAssignFilesDeterminism: + """Verify file assignment is deterministic for same-depth scopes.""" + + def test_same_depth_scopes_deterministic(self): + """Scopes at the same depth should assign files consistently.""" + from codespy.agents.reviewer.models import ScopeResult, ScopeType + + # Two scopes at depth 1 (one slash each) + scope_a = ScopeResult(subroot="packages/alpha", scope_type=ScopeType.LIBRARY, reason="test") + scope_b = ScopeResult(subroot="packages/beta", scope_type=ScopeType.LIBRARY, reason="test") + file_alpha = ChangedFile(filename="packages/alpha/index.ts", status=FileStatus.MODIFIED) + file_beta = ChangedFile(filename="packages/beta/index.ts", status=FileStatus.MODIFIED) + + resolver = ScopeResolver() + # Pass scopes in both orders — assignment should be identical + orphans_ab = resolver._assign_files([scope_a, scope_b], [file_alpha, file_beta]) + scope_a.changed_files.clear() + scope_b.changed_files.clear() + orphans_ba = resolver._assign_files([scope_b, scope_a], [file_alpha, file_beta]) + + assert len(orphans_ab) == 0 + assert len(orphans_ba) == 0 + assert scope_a.changed_files == [file_alpha] + assert scope_b.changed_files == [file_beta] + + +if __name__ == "__main__": + pytest.main([__file__, "-v"])