diff --git a/.env.dev b/.env.dev deleted file mode 100644 index 2a13ae67..00000000 --- a/.env.dev +++ /dev/null @@ -1,21 +0,0 @@ -# Development/Production environment - -# Database -DATABASE_URL=postgresql://postgres.goflmrvzwagridonyxxn:OjUtCVVtoV0nGPPP@aws-1-us-east-1.pooler.supabase.com:6543/postgres - -# Disable tokenizers parallelism warning (happens with forked processes) -TOKENIZERS_PARALLELISM=false - -# Main LLM Configuration (for memory operations: put/think/opinions) -# Choose one: "openai", "groq", or "ollama" -MEMORY_LLM_PROVIDER=groq -MEMORY_LLM_API_KEY=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 -MEMORY_LLM_MODEL=openai/gpt-oss-120b -# MEMORY_LLM_BASE_URL=http://localhost:11434/v1 # For ollama or custom endpoints - -# Judge LLM Configuration (for benchmark evaluation) -# If not set, falls back to main LLM configuration -JUDGE_LLM_PROVIDER=groq -JUDGE_LLM_API_KEY=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 -JUDGE_LLM_MODEL=openai/gpt-oss-120b -# JUDGE_LLM_BASE_URL=https://api.custom.com/v1 # Optional custom endpoint diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..143e32df --- /dev/null +++ b/.env.example @@ -0,0 +1,39 @@ +# ============================================================================= +# MEMORA ENVIRONMENT CONFIGURATION +# ============================================================================= +# Copy this file to .env and update with your values +# Both services (API and Control Plane) read from this single file + +# ============================================================================= +# API SERVICE (MEMORA_API_*) +# ============================================================================= + +# Database +MEMORA_API_DATABASE_URL=postgresql://memora:memora_dev@localhost:5432/memora + +# LLM Provider: "openai", "groq", or "ollama" +MEMORA_API_LLM_PROVIDER=groq + +# LLM Model (provider-specific) +MEMORA_API_LLM_MODEL=openai/gpt-oss-20b + +# API Key (not needed for ollama) +MEMORA_API_LLM_API_KEY=your_api_key_here + +# Optional: Custom base URL (for ollama or custom endpoints) +# MEMORA_API_LLM_BASE_URL=http://localhost:11434/v1 + +# API Server Configuration (optional) +# MEMORA_API_HOST=0.0.0.0 +# MEMORA_API_PORT=8080 + +# ============================================================================= +# CONTROL PLANE SERVICE (MEMORA_CP_*) +# ============================================================================= + +# Dataplane API URL (where the control plane connects to) +MEMORA_CP_DATAPLANE_API_URL=http://localhost:8080 + +# Control Plane Server Configuration (optional) +# MEMORA_CP_PORT=3000 +# MEMORA_CP_HOSTNAME=0.0.0.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..ffd540da --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,225 @@ +name: Build Release Artifacts + +on: + push: + tags: + - 'v*' + +jobs: + build-python-packages: + runs-on: ubuntu-latest + strategy: + matrix: + package: [memora, benchmarks, memora-dev] + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: ".python-version" + + - name: Build package + run: | + cd ${{ matrix.package }} + uv build + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: python-${{ matrix.package }}-dist + path: ${{ matrix.package }}/dist/* + retention-days: 30 + + build-rust-cli: + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + artifact_name: memora + asset_name: memora-linux-amd64 + - os: macos-latest + target: x86_64-apple-darwin + artifact_name: memora + asset_name: memora-darwin-amd64 + - os: macos-latest + target: aarch64-apple-darwin + artifact_name: memora + asset_name: memora-darwin-arm64 + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache cargo index + uses: actions/cache@v4 + with: + path: ~/.cargo/git + key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache cargo build + uses: actions/cache@v4 + with: + path: memora-cli/target + key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + + - name: Build + working-directory: memora-cli + run: cargo build --release --target ${{ matrix.target }} + + - name: Prepare artifact + run: | + mkdir -p artifacts + cp memora-cli/target/${{ matrix.target }}/release/${{ matrix.artifact_name }} artifacts/${{ matrix.asset_name }} + chmod +x artifacts/${{ matrix.asset_name }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: rust-cli-${{ matrix.asset_name }} + path: artifacts/${{ matrix.asset_name }} + retention-days: 30 + + build-control-plane: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: memora-control-plane/package-lock.json + + - name: Install dependencies + working-directory: ./memora-control-plane + run: npm ci + + - name: Build Next.js app + working-directory: ./memora-control-plane + run: npm run build + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: control-plane-build + path: | + memora-control-plane/.next/standalone + memora-control-plane/.next/static + retention-days: 30 + + build-docker-images: + runs-on: ubuntu-latest + strategy: + matrix: + component: [standalone, control-plane] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Extract version from tag + id: get_version + run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + + - name: Build Docker image (standalone) + if: matrix.component == 'standalone' + uses: docker/build-push-action@v6 + with: + context: . + file: standalone/Dockerfile + push: false + tags: memora-standalone:${{ steps.get_version.outputs.VERSION }} + cache-from: type=gha + cache-to: type=gha,mode=max + outputs: type=docker,dest=/tmp/memora-standalone.tar + + - name: Build Docker image (control-plane) + if: matrix.component == 'control-plane' + uses: docker/build-push-action@v6 + with: + context: ./memora-control-plane + file: memora-control-plane/Dockerfile + push: false + tags: memora-control-plane:${{ steps.get_version.outputs.VERSION }} + cache-from: type=gha + cache-to: type=gha,mode=max + outputs: type=docker,dest=/tmp/memora-control-plane.tar + + - name: Upload Docker image artifact + uses: actions/upload-artifact@v4 + with: + name: docker-image-${{ matrix.component }} + path: /tmp/memora-${{ matrix.component }}.tar + retention-days: 30 + + package-helm-chart: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Helm + uses: azure/setup-helm@v4 + with: + version: 'latest' + + - name: Lint Helm chart + run: | + helm lint helm/memora + + - name: Package Helm chart + run: | + helm package helm/memora --destination ./helm-packages + + - name: Upload Helm chart artifact + uses: actions/upload-artifact@v4 + with: + name: helm-chart + path: helm-packages/*.tgz + retention-days: 30 + + create-release-summary: + runs-on: ubuntu-latest + needs: [build-python-packages, build-rust-cli, build-control-plane, build-docker-images, package-helm-chart] + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: ./artifacts + + - name: Create release summary + run: | + echo "# Release Artifacts Built Successfully" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Components" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Python packages (memora, benchmarks, memora-dev)" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Rust CLI (Linux amd64, macOS amd64, macOS arm64)" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Control Plane Next.js application" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Docker images (standalone, control-plane)" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Helm chart" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "All artifacts are available for download in the workflow artifacts." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..4e4ecc46 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,56 @@ +name: Run Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: pgvector/pgvector:pg16 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: memora_test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + env: + MEMORA_API_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/memora_test + MEMORA_API_LLM_PROVIDER: groq + MEMORA_API_LLM_API_KEY: ${{ secrets.GROQ_API_KEY }} + MEMORA_API_LLM_MODEL: openai/gpt-oss-120b + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: ".python-version" + + - name: Install dependencies + run: uv sync --all-extras --dev + + - name: Run migrations + working-directory: ./memora + run: | + uv run alembic upgrade head + + - name: Run tests + run: uv run pytest memora/tests -v diff --git a/.gitignore b/.gitignore index 810480ed..23edfb83 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ wheels/ # Environment variables .env -.env.local # IDE .idea/ diff --git a/memora/README.md b/README.md similarity index 94% rename from memora/README.md rename to README.md index 7b87c7fe..11803a0f 100644 --- a/memora/README.md +++ b/README.md @@ -201,39 +201,28 @@ Raw content is processed through an LLM (Groq by default) to extract meaningful uv sync ``` -2. Configure environment files: +2. Configure environment file: -Create `.env.local` for local development: +Create `.env` file: ```bash -cat > .env.local << 'EOF' -# Database -DATABASE_URL=postgresql://memora:memora_dev@localhost:5432/memora +cat > .env << 'EOF' +# API Service Configuration +MEMORA_API_DATABASE_URL=postgresql://memora:memora_dev@localhost:5432/memora # LLM Provider: "openai", "groq", or "ollama" -LLM_PROVIDER=groq +MEMORA_API_LLM_PROVIDER=groq # API Key (not needed for ollama) -LLM_API_KEY=your_api_key_here +MEMORA_API_LLM_API_KEY=your_api_key_here + +# LLM Model +MEMORA_API_LLM_MODEL=openai/gpt-oss-120b # Optional: Custom base URL (for ollama or custom endpoints) -# LLM_BASE_URL=http://localhost:11434/v1 -EOF -``` +# MEMORA_API_LLM_BASE_URL=http://localhost:11434/v1 -Create `.env.dev` for dev/production environment: -```bash -cat > .env.dev << 'EOF' -# Database -DATABASE_URL=postgresql://user:password@host:5432/memora - -# LLM Provider: "openai", "groq", or "ollama" -LLM_PROVIDER=groq - -# API Key (not needed for ollama) -LLM_API_KEY=your_api_key_here - -# Optional: Custom base URL -# LLM_BASE_URL=https://api.custom-provider.com/v1 +# Control Plane Configuration +MEMORA_CP_DATAPLANE_API_URL=http://localhost:8080 EOF ``` @@ -286,17 +275,23 @@ JUDGE_LLM_API_KEY=your_openai_key ### Local Development ```bash -# Start local PostgreSQL (with initialization) -./scripts/start-local-db.sh +# Start all services with Docker (PostgreSQL, API, Control Plane) +cd ../docker +./start.sh -# Start the server with local environment (default) +# Or start services individually: +# 1. Start PostgreSQL only +# (then migrations run automatically when API starts) +# 2. Start the server with local environment ./scripts/start-server.sh --env local -# Start the server with dev environment -./scripts/start-server.sh --env dev +# Stop all Docker services +cd ../docker +./stop.sh -# Erase local database (stop + cleanup) -./scripts/erase-local-db.sh +# Erase all data and containers +cd ../docker +./clean.sh ``` The server will start at http://localhost:8080 diff --git a/control-plane/.env.local.example b/control-plane/.env.local.example deleted file mode 100644 index 3a8e2a34..00000000 --- a/control-plane/.env.local.example +++ /dev/null @@ -1,3 +0,0 @@ -# Dataplane API Configuration -# URL of the Python FastAPI dataplane server (server-side only, not exposed to browser) -DATAPLANE_API_URL=http://localhost:8080 diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..8cecd57a --- /dev/null +++ b/docker/README.md @@ -0,0 +1,175 @@ +# Memora Docker Setup + +Complete Docker Compose setup for running all Memora services locally. + +## Services + +This setup includes: +- **PostgreSQL** with pgvector extension (port 5432) +- **API Service** - FastAPI backend (port 8080) +- **Control Plane** - Next.js web UI (port 3000) + +## Quick Start + +1. **Configure environment variables:** + ```bash + cp .env.example .env + # Edit .env and set your API keys + ``` + +2. **Start all services:** + ```bash + ./start.sh + ``` + +3. **Access the services:** + - Control Plane: http://localhost:3000 + - API: http://localhost:8080 + - PostgreSQL: localhost:5432 + +## Scripts + +### `./start.sh` +Build and start all services. Waits for all services to be healthy. + +### `./stop.sh` +Stop all services (keeps data). + +### `./clean.sh` +Stop all services and remove all data (destructive). + +### `./logs.sh [service]` +View logs for all services or a specific service: +```bash +./logs.sh # All services +./logs.sh api # API only +./logs.sh postgres # PostgreSQL only +./logs.sh control-plane # Control plane only +``` + +## Manual Docker Compose Commands + +```bash +# Start services +docker-compose up -d + +# Stop services +docker-compose down + +# Rebuild and start +docker-compose up --build -d + +# View logs +docker-compose logs -f + +# Remove everything including data +docker-compose down -v +``` + +## Database + +### Connection Info +- **Host:** localhost +- **Port:** 5432 +- **Database:** memora +- **User:** memora +- **Password:** memora_dev + +### Migrations + +Database migrations run automatically when the API service starts. The API uses Alembic to: +1. Check the current schema version +2. Run any pending migrations +3. Initialize the database if it's empty + +Extensions (pgvector, uuid-ossp) are created automatically by the first migration. + +## Environment Variables + +Required in `.env` file: + +```bash +# API Service Configuration +MEMORA_API_DATABASE_URL=postgresql://memora:memora_dev@localhost:5432/memora +MEMORA_API_LLM_PROVIDER=groq +MEMORA_API_LLM_API_KEY=your-api-key-here +MEMORA_API_LLM_MODEL=openai/gpt-oss-120b + +# Optional: Custom LLM endpoint +# MEMORA_API_LLM_BASE_URL=http://localhost:11434/v1 + +# Control Plane Configuration +MEMORA_CP_DATAPLANE_API_URL=http://localhost:8080 +``` + +## Troubleshooting + +### Services won't start +Check logs for errors: +```bash +./logs.sh +``` + +### Database connection issues +Ensure PostgreSQL is healthy: +```bash +docker exec memora-postgres pg_isready -U memora +``` + +### API won't connect to database +Check if migrations ran successfully: +```bash +./logs.sh api +``` + +### Control plane can't reach API +Verify the API is running: +```bash +curl http://localhost:8080/ +``` + +### Reset everything +```bash +./clean.sh +./start.sh +``` + +## Development + +### Rebuilding after code changes + +**API changes:** +```bash +docker-compose up --build -d api +``` + +**Control Plane changes:** +```bash +docker-compose up --build -d control-plane +``` + +### Accessing the database +```bash +docker exec -it memora-postgres psql -U memora -d memora +``` + +### Inspecting containers +```bash +docker-compose ps +docker-compose exec api bash +docker-compose exec control-plane sh +``` + +## Data Persistence + +PostgreSQL data is persisted in a Docker volume named `postgres_data`. This data survives container restarts but not `docker-compose down -v`. + +To backup data: +```bash +docker exec memora-postgres pg_dump -U memora memora > backup.sql +``` + +To restore data: +```bash +docker exec -i memora-postgres psql -U memora memora < backup.sql +``` diff --git a/docker/api.Dockerfile b/docker/api.Dockerfile new file mode 100644 index 00000000..914c84f7 --- /dev/null +++ b/docker/api.Dockerfile @@ -0,0 +1,27 @@ +FROM python:3.11-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + curl \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +# Set working directory +WORKDIR /app + +# Copy project files +COPY memora /app/memora + +# Install Python dependencies +WORKDIR /app/memora +RUN pip install --no-cache-dir -e . + +# Expose API port +EXPOSE 8080 + +# Set environment variables +ENV PYTHONUNBUFFERED=1 +ENV DATABASE_URL=postgresql://memora:memora_dev@postgres:5432/memora + +# Run the API server +CMD ["python", "-m", "memora.web.server", "--host", "0.0.0.0", "--port", "8080"] diff --git a/docker/clean.sh b/docker/clean.sh new file mode 100755 index 00000000..d78a0b46 --- /dev/null +++ b/docker/clean.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -e + +cd "$(dirname "$0")" + +echo "🧹 Cleaning Memora Services" +echo "============================" +echo "" +echo "This will:" +echo " - Stop all services" +echo " - Remove containers" +echo " - Remove volumes (ALL DATA WILL BE LOST)" +echo "" +read -p "Are you sure? (yes/no): " confirm + +if [ "$confirm" != "yes" ]; then + echo "Cancelled." + exit 0 +fi + +echo "" +echo "🗑️ Removing services and data..." +docker-compose down -v + +echo "" +echo "✅ All services and data removed" +echo "" diff --git a/control-plane/Dockerfile b/docker/control-plane.Dockerfile similarity index 100% rename from control-plane/Dockerfile rename to docker/control-plane.Dockerfile diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000..6897277f --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,79 @@ +services: + postgres: + image: pgvector/pgvector:pg16 + container_name: memora-postgres + environment: + POSTGRES_USER: memora + POSTGRES_PASSWORD: memora_dev + POSTGRES_DB: memora + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U memora"] + interval: 5s + timeout: 5s + retries: 5 + networks: + - memora-network + + api: + build: + context: .. + dockerfile: docker/api.Dockerfile + container_name: memora-api + environment: + MEMORA_API_DATABASE_URL: postgresql://memora:memora_dev@postgres:5432/memora + MEMORA_API_LLM_PROVIDER: ${MEMORA_API_LLM_PROVIDER:-groq} + MEMORA_API_LLM_API_KEY: ${MEMORA_API_LLM_API_KEY} + MEMORA_API_LLM_MODEL: ${MEMORA_API_LLM_MODEL:-openai/gpt-oss-120b} + MEMORA_API_LLM_BASE_URL: ${MEMORA_API_LLM_BASE_URL} + MEMORA_API_HOST: ${MEMORA_API_HOST:-0.0.0.0} + MEMORA_API_PORT: ${MEMORA_API_PORT:-8080} + ports: + - "8080:8080" + depends_on: + postgres: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - memora-network + restart: unless-stopped + + control-plane: + build: + context: ../memora-control-plane + dockerfile: ../docker/control-plane.Dockerfile + container_name: memora-control-plane + environment: + NODE_ENV: production + MEMORA_CP_HOSTNAME: ${MEMORA_CP_HOSTNAME:-0.0.0.0} + MEMORA_CP_PORT: ${MEMORA_CP_PORT:-3000} + MEMORA_CP_DATAPLANE_API_URL: ${MEMORA_CP_DATAPLANE_API_URL:-http://api:8080} + ports: + - "3000:3000" + depends_on: + api: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - memora-network + restart: unless-stopped + +networks: + memora-network: + driver: bridge + +volumes: + postgres_data: diff --git a/docker/logs.sh b/docker/logs.sh new file mode 100755 index 00000000..cb705dfb --- /dev/null +++ b/docker/logs.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +cd "$(dirname "$0")" + +SERVICE=$1 + +if [ -z "$SERVICE" ]; then + echo "📋 Showing logs for all services..." + echo "" + docker-compose logs -f +else + echo "📋 Showing logs for $SERVICE..." + echo "" + docker-compose logs -f "$SERVICE" +fi diff --git a/docker/start.sh b/docker/start.sh new file mode 100755 index 00000000..247121ed --- /dev/null +++ b/docker/start.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -e + +cd "$(dirname "$0")" + +echo "🚀 Starting Memora Services" +echo "============================" +echo "" + +# Check if .env file exists in root +if [ ! -f ../.env ]; then + echo "⚠️ No .env file found in project root!" + echo "" + echo "Creating .env from .env.example..." + cp ../.env.example ../.env + echo "" + echo "⚠️ Please edit .env and set your API keys:" + echo " - MEMORY_LLM_API_KEY" + echo "" + echo "Then run this script again." + exit 1 +fi + +echo "📦 Building and starting services..." +docker-compose --env-file ../.env up --build -d + +echo "" +echo "⏳ Waiting for services to be healthy..." +echo "" + +# Wait for PostgreSQL +echo " Waiting for PostgreSQL..." +until docker exec memora-postgres pg_isready -U memora > /dev/null 2>&1; do + sleep 1 +done +echo " ✅ PostgreSQL is ready" + +# Wait for API +echo " Waiting for API..." +until curl -f http://localhost:8080/ > /dev/null 2>&1; do + sleep 2 +done +echo " ✅ API is ready" + +# Wait for Control Plane +echo " Waiting for Control Plane..." +until curl -f http://localhost:3000/ > /dev/null 2>&1; do + sleep 2 +done +echo " ✅ Control Plane is ready" + +echo "" +echo "✅ All services are running!" +echo "" +echo "📊 Service URLs:" +echo " Control Plane: http://localhost:3000" +echo " API: http://localhost:8080" +echo " PostgreSQL: localhost:5432" +echo "" +echo "🔍 View logs:" +echo " docker-compose logs -f" +echo "" +echo "🛑 Stop services:" +echo " ./stop.sh" +echo "" diff --git a/docker/stop.sh b/docker/stop.sh new file mode 100755 index 00000000..d563d26c --- /dev/null +++ b/docker/stop.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -e + +cd "$(dirname "$0")" + +echo "🛑 Stopping Memora Services" +echo "============================" +echo "" + +docker-compose down + +echo "" +echo "✅ All services stopped" +echo "" +echo "💡 To remove data volumes as well, run:" +echo " docker-compose down -v" +echo "" diff --git a/helm/INSTALL.txt b/helm/INSTALL.txt new file mode 100644 index 00000000..05bfd5d3 --- /dev/null +++ b/helm/INSTALL.txt @@ -0,0 +1,135 @@ +MEMORA HELM CHART INSTALLATION GUIDE +===================================== + +PREREQUISITES +------------- +- Kubernetes cluster (1.19+) +- kubectl configured +- Helm 3.x installed +- PostgreSQL database with pgvector extension (if not using bundled PostgreSQL) + +BASIC INSTALLATION +------------------ + +1. Install with default values (requires external PostgreSQL): + + helm install memora ./memora \ + --set postgresql.external.host=your-postgres-host \ + --set postgresql.external.password=your-password \ + --set api.secrets.MEMORY_LLM_API_KEY=your-api-key + +2. Install with custom values file: + + helm install memora ./memora -f memora/values-production.yaml + +3. Install in a specific namespace: + + kubectl create namespace memora + helm install memora ./memora -n memora + +CONFIGURATION OPTIONS +--------------------- + +Development setup (using values-development.yaml): + helm install memora ./memora -f memora/values-development.yaml + +Production setup (using values-production.yaml): + helm install memora ./memora -f memora/values-production.yaml + +Custom LLM provider: + helm install memora ./memora \ + --set api.env.MEMORY_LLM_PROVIDER=openai \ + --set api.env.MEMORY_LLM_MODEL=gpt-4 \ + --set api.secrets.MEMORY_LLM_API_KEY=sk-your-key + +Enable ingress: + helm install memora ./memora \ + --set ingress.enabled=true \ + --set ingress.hosts[0].host=memora.example.com + +Enable autoscaling: + helm install memora ./memora \ + --set autoscaling.enabled=true \ + --set autoscaling.minReplicas=2 \ + --set autoscaling.maxReplicas=10 + +UPGRADE +------- + +Upgrade existing installation: + helm upgrade memora ./memora + +Upgrade with new values: + helm upgrade memora ./memora -f memora/values-production.yaml + +UNINSTALL +--------- + +Remove the Helm release: + helm uninstall memora + +Remove with namespace: + helm uninstall memora -n memora + +TESTING +------- + +Test the installation with dry-run: + helm install memora ./memora --dry-run --debug + +Validate templates: + helm template memora ./memora + +Lint the chart: + helm lint ./memora + +ACCESSING THE SERVICES +---------------------- + +Port-forward control plane: + kubectl port-forward svc/memora-control-plane 3000:3000 + +Port-forward API: + kubectl port-forward svc/memora-api 8080:8080 + +Get service URLs: + helm status memora + +DATABASE INITIALIZATION +----------------------- + +NOTE: Database migrations now run automatically when the API service starts. + You typically don't need to run migrations manually. + +If you want to pre-initialize the database before deploying (optional): + kubectl run memora-init --rm -it --restart=Never \ + --image=memora/api:latest \ + --env="DATABASE_URL=postgresql://user:pass@host:5432/memora" \ + -- python -c "from memora.migrations import run_migrations; run_migrations()" + +TROUBLESHOOTING +--------------- + +Check pod status: + kubectl get pods -l app.kubernetes.io/name=memora + +View logs for API: + kubectl logs -l app.kubernetes.io/component=api + +View logs for control plane: + kubectl logs -l app.kubernetes.io/component=control-plane + +Describe a pod: + kubectl describe pod + +Check configuration: + kubectl get configmap memora-config -o yaml + kubectl get secret memora-secret -o yaml + +NOTES +----- +- Make sure PostgreSQL has pgvector extension enabled +- Run database migrations before first use +- Configure proper resource limits for production +- Use external secrets management for production +- Enable TLS/SSL for production deployments diff --git a/helm/memora/.helmignore b/helm/memora/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/helm/memora/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/memora/Chart.yaml b/helm/memora/Chart.yaml new file mode 100644 index 00000000..476b9339 --- /dev/null +++ b/helm/memora/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +name: memora +description: A Helm chart for Memora - temporal-semantic-entity memory system for AI agents +type: application +version: 0.1.0 +appVersion: "1.0.0" +keywords: + - ai + - memory + - llm + - agents +maintainers: + - name: Memora Team diff --git a/helm/memora/templates/NOTES.txt b/helm/memora/templates/NOTES.txt new file mode 100644 index 00000000..141170e3 --- /dev/null +++ b/helm/memora/templates/NOTES.txt @@ -0,0 +1,71 @@ +Thank you for installing {{ .Chart.Name }}! + +Your release is named {{ .Release.Name }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get all {{ .Release.Name }} + +{{- if .Values.ingress.enabled }} + +The application is accessible via the following URL(s): +{{- range .Values.ingress.hosts }} + - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }} +{{- end }} + +{{- else }} + +1. Get the Control Plane URL by running these commands: +{{- if contains "NodePort" .Values.controlPlane.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "memora.fullname" . }}-control-plane) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo "Control Plane URL: http://$NODE_IP:$NODE_PORT" +{{- else if contains "LoadBalancer" .Values.controlPlane.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "memora.fullname" . }}-control-plane' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "memora.fullname" . }}-control-plane --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo "Control Plane URL: http://$SERVICE_IP:{{ .Values.controlPlane.service.port }}" +{{- else if contains "ClusterIP" .Values.controlPlane.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/component=control-plane,app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Control Plane URL: http://127.0.0.1:3000" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 3000:$CONTAINER_PORT +{{- end }} + +2. Get the API URL by running these commands: +{{- if contains "NodePort" .Values.api.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "memora.fullname" . }}-api) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo "API URL: http://$NODE_IP:$NODE_PORT" +{{- else if contains "LoadBalancer" .Values.api.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "memora.fullname" . }}-api' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "memora.fullname" . }}-api --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo "API URL: http://$SERVICE_IP:{{ .Values.api.service.port }}" +{{- else if contains "ClusterIP" .Values.api.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/component=api,app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "API URL: http://127.0.0.1:8080" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} + +{{- end }} + +{{- if not .Values.postgresql.enabled }} + +NOTE: You are using an external PostgreSQL database. +Please ensure that: + 1. The database is accessible from the cluster + 2. The pgvector extension is enabled + +Database migrations run automatically when the API service starts. + +If you want to pre-initialize the database before deploying (optional): + kubectl run --namespace {{ .Release.Namespace }} memora-init --rm -it --restart=Never \ + --image={{ .Values.api.image.repository }}:{{ .Values.api.image.tag }} \ + --env="DATABASE_URL={{ include "memora.databaseUrl" . }}" \ + -- python -c "from memora.migrations import run_migrations; run_migrations()" +{{- end }} + +For more information, visit: https://github.com/yourusername/memora diff --git a/helm/memora/templates/_helpers.tpl b/helm/memora/templates/_helpers.tpl new file mode 100644 index 00000000..eec3da6c --- /dev/null +++ b/helm/memora/templates/_helpers.tpl @@ -0,0 +1,112 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "memora.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "memora.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "memora.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "memora.labels" -}} +helm.sh/chart: {{ include "memora.chart" . }} +{{ include "memora.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "memora.selectorLabels" -}} +app.kubernetes.io/name: {{ include "memora.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +API labels +*/}} +{{- define "memora.api.labels" -}} +{{ include "memora.labels" . }} +app.kubernetes.io/component: api +{{- end }} + +{{/* +API selector labels +*/}} +{{- define "memora.api.selectorLabels" -}} +{{ include "memora.selectorLabels" . }} +app.kubernetes.io/component: api +{{- end }} + +{{/* +Control plane labels +*/}} +{{- define "memora.controlPlane.labels" -}} +{{ include "memora.labels" . }} +app.kubernetes.io/component: control-plane +{{- end }} + +{{/* +Control plane selector labels +*/}} +{{- define "memora.controlPlane.selectorLabels" -}} +{{ include "memora.selectorLabels" . }} +app.kubernetes.io/component: control-plane +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "memora.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "memora.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Generate database URL +*/}} +{{- define "memora.databaseUrl" -}} +{{- if .Values.databaseUrl }} +{{- .Values.databaseUrl }} +{{- else if .Values.postgresql.enabled }} +{{- printf "postgresql://%s:%s@%s-postgresql:%d/%s" .Values.postgresql.auth.username .Values.postgresql.auth.password (include "memora.fullname" .) (.Values.postgresql.primary.service.port | int) .Values.postgresql.auth.database }} +{{- else }} +{{- printf "postgresql://%s:$(POSTGRES_PASSWORD)@%s:%d/%s" .Values.postgresql.external.username .Values.postgresql.external.host (.Values.postgresql.external.port | int) .Values.postgresql.external.database }} +{{- end }} +{{- end }} + +{{/* +API URL for control plane +*/}} +{{- define "memora.apiUrl" -}} +{{- printf "http://%s-api:%d" (include "memora.fullname" .) (.Values.api.service.port | int) }} +{{- end }} diff --git a/helm/memora/templates/api-deployment.yaml b/helm/memora/templates/api-deployment.yaml new file mode 100644 index 00000000..89394038 --- /dev/null +++ b/helm/memora/templates/api-deployment.yaml @@ -0,0 +1,93 @@ +{{- if .Values.api.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "memora.fullname" . }}-api + labels: + {{- include "memora.api.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.api.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "memora.api.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "memora.api.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.serviceAccount.create }} + serviceAccountName: {{ include "memora.serviceAccountName" . }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: api + securityContext: + {{- toYaml .Values.securityContext | nindent 10 }} + image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}" + imagePullPolicy: {{ .Values.api.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.api.service.targetPort }} + protocol: TCP + env: + - name: MEMORA_API_DATABASE_URL + value: {{ include "memora.databaseUrl" . | quote }} + {{- if not .Values.postgresql.enabled }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "memora.fullname" . }}-secret + key: postgres-password + {{- end }} + - name: MEMORA_API_LLM_PROVIDER + valueFrom: + configMapKeyRef: + name: {{ include "memora.fullname" . }}-config + key: llm-provider + - name: MEMORA_API_LLM_MODEL + valueFrom: + configMapKeyRef: + name: {{ include "memora.fullname" . }}-config + key: llm-model + {{- if and .Values.api.secrets (hasKey .Values.api.secrets "MEMORA_API_LLM_API_KEY") }} + - name: MEMORA_API_LLM_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "memora.fullname" . }}-secret + key: llm-api-key + {{- end }} + {{- if and .Values.api.secrets (hasKey .Values.api.secrets "MEMORA_API_LLM_BASE_URL") }} + - name: MEMORA_API_LLM_BASE_URL + valueFrom: + secretKeyRef: + name: {{ include "memora.fullname" . }}-secret + key: llm-base-url + {{- end }} + livenessProbe: + {{- toYaml .Values.api.livenessProbe | nindent 10 }} + readinessProbe: + {{- toYaml .Values.api.readinessProbe | nindent 10 }} + resources: + {{- toYaml .Values.api.resources | nindent 10 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/helm/memora/templates/api-service.yaml b/helm/memora/templates/api-service.yaml new file mode 100644 index 00000000..ecfa9dd6 --- /dev/null +++ b/helm/memora/templates/api-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.api.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "memora.fullname" . }}-api + labels: + {{- include "memora.api.labels" . | nindent 4 }} +spec: + type: {{ .Values.api.service.type }} + ports: + - port: {{ .Values.api.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "memora.api.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/helm/memora/templates/configmap.yaml b/helm/memora/templates/configmap.yaml new file mode 100644 index 00000000..bb7f2c59 --- /dev/null +++ b/helm/memora/templates/configmap.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "memora.fullname" . }}-config + labels: + {{- include "memora.labels" . | nindent 4 }} +data: + # API configuration + llm-provider: {{ .Values.api.env.MEMORA_API_LLM_PROVIDER | quote }} + llm-model: {{ .Values.api.env.MEMORA_API_LLM_MODEL | quote }} + + # Control plane configuration + node-env: {{ .Values.controlPlane.env.NODE_ENV | quote }} + hostname: {{ .Values.controlPlane.env.MEMORA_CP_HOSTNAME | quote }} + control-plane-port: {{ .Values.controlPlane.env.MEMORA_CP_PORT | quote }} diff --git a/helm/memora/templates/controlplane-deployment.yaml b/helm/memora/templates/controlplane-deployment.yaml new file mode 100644 index 00000000..0890da4b --- /dev/null +++ b/helm/memora/templates/controlplane-deployment.yaml @@ -0,0 +1,76 @@ +{{- if .Values.controlPlane.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "memora.fullname" . }}-control-plane + labels: + {{- include "memora.controlPlane.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.controlPlane.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "memora.controlPlane.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "memora.controlPlane.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.serviceAccount.create }} + serviceAccountName: {{ include "memora.serviceAccountName" . }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: control-plane + securityContext: + {{- toYaml .Values.securityContext | nindent 10 }} + image: "{{ .Values.controlPlane.image.repository }}:{{ .Values.controlPlane.image.tag }}" + imagePullPolicy: {{ .Values.controlPlane.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.controlPlane.service.targetPort }} + protocol: TCP + env: + - name: NODE_ENV + valueFrom: + configMapKeyRef: + name: {{ include "memora.fullname" . }}-config + key: node-env + - name: MEMORA_CP_HOSTNAME + valueFrom: + configMapKeyRef: + name: {{ include "memora.fullname" . }}-config + key: hostname + - name: MEMORA_CP_PORT + valueFrom: + configMapKeyRef: + name: {{ include "memora.fullname" . }}-config + key: control-plane-port + - name: MEMORA_CP_DATAPLANE_API_URL + value: {{ include "memora.apiUrl" . | quote }} + livenessProbe: + {{- toYaml .Values.controlPlane.livenessProbe | nindent 10 }} + readinessProbe: + {{- toYaml .Values.controlPlane.readinessProbe | nindent 10 }} + resources: + {{- toYaml .Values.controlPlane.resources | nindent 10 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/helm/memora/templates/controlplane-service.yaml b/helm/memora/templates/controlplane-service.yaml new file mode 100644 index 00000000..87ac7600 --- /dev/null +++ b/helm/memora/templates/controlplane-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.controlPlane.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "memora.fullname" . }}-control-plane + labels: + {{- include "memora.controlPlane.labels" . | nindent 4 }} +spec: + type: {{ .Values.controlPlane.service.type }} + ports: + - port: {{ .Values.controlPlane.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "memora.controlPlane.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/helm/memora/templates/hpa.yaml b/helm/memora/templates/hpa.yaml new file mode 100644 index 00000000..bfb87394 --- /dev/null +++ b/helm/memora/templates/hpa.yaml @@ -0,0 +1,64 @@ +{{- if .Values.autoscaling.enabled }} +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "memora.fullname" . }}-api + labels: + {{- include "memora.api.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "memora.fullname" . }}-api + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "memora.fullname" . }}-control-plane + labels: + {{- include "memora.controlPlane.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "memora.fullname" . }}-control-plane + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/memora/templates/ingress.yaml b/helm/memora/templates/ingress.yaml new file mode 100644 index 00000000..81905e13 --- /dev/null +++ b/helm/memora/templates/ingress.yaml @@ -0,0 +1,47 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "memora.fullname" . }} + labels: + {{- include "memora.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + {{- if eq .service "api" }} + name: {{ include "memora.fullname" $ }}-api + port: + number: {{ $.Values.api.service.port }} + {{- else if eq .service "controlPlane" }} + name: {{ include "memora.fullname" $ }}-control-plane + port: + number: {{ $.Values.controlPlane.service.port }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/memora/templates/secret.yaml b/helm/memora/templates/secret.yaml new file mode 100644 index 00000000..89433dda --- /dev/null +++ b/helm/memora/templates/secret.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "memora.fullname" . }}-secret + labels: + {{- include "memora.labels" . | nindent 4 }} +type: Opaque +data: + {{- if and .Values.api.secrets (hasKey .Values.api.secrets "MEMORY_LLM_API_KEY") }} + llm-api-key: {{ .Values.api.secrets.MEMORY_LLM_API_KEY | b64enc | quote }} + {{- end }} + {{- if and .Values.api.secrets (hasKey .Values.api.secrets "MEMORY_LLM_BASE_URL") }} + llm-base-url: {{ .Values.api.secrets.MEMORY_LLM_BASE_URL | b64enc | quote }} + {{- end }} + {{- if not .Values.postgresql.enabled }} + {{- if .Values.postgresql.external.password }} + postgres-password: {{ .Values.postgresql.external.password | b64enc | quote }} + {{- end }} + {{- end }} diff --git a/helm/memora/templates/serviceaccount.yaml b/helm/memora/templates/serviceaccount.yaml new file mode 100644 index 00000000..fb6d1b09 --- /dev/null +++ b/helm/memora/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "memora.serviceAccountName" . }} + labels: + {{- include "memora.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/memora/values.yaml b/helm/memora/values.yaml new file mode 100644 index 00000000..bc3be3f7 --- /dev/null +++ b/helm/memora/values.yaml @@ -0,0 +1,185 @@ +# Default values for memora + +# Global settings +replicaCount: 1 + +# Image settings for api +api: + enabled: true + replicaCount: 1 + image: + repository: memora/api + pullPolicy: IfNotPresent + tag: "latest" + + service: + type: ClusterIP + port: 8080 + targetPort: 8080 + + # Resource limits and requests + resources: + limits: + cpu: 2000m + memory: 4Gi + requests: + cpu: 500m + memory: 1Gi + + # Liveness and readiness probes + livenessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + + readinessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + + # Environment variables + env: + MEMORA_API_LLM_PROVIDER: "groq" + MEMORA_API_LLM_MODEL: "openai/gpt-oss-120b" + + # Secret environment variables + secrets: + # MEMORA_API_LLM_API_KEY: "your-api-key" + # MEMORA_API_LLM_BASE_URL: "https://api.groq.com/openai/v1" + +# Image settings for control plane +controlPlane: + enabled: true + replicaCount: 1 + image: + repository: memora/memora-control-plane + pullPolicy: IfNotPresent + tag: "latest" + + service: + type: ClusterIP + port: 3000 + targetPort: 3000 + + # Resource limits and requests + resources: + limits: + cpu: 1000m + memory: 2Gi + requests: + cpu: 250m + memory: 512Mi + + # Liveness and readiness probes + livenessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + + readinessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + + # Environment variables + env: + NODE_ENV: "production" + MEMORA_CP_HOSTNAME: "0.0.0.0" + MEMORA_CP_PORT: "3000" + +# PostgreSQL configuration +postgresql: + # Set to true to deploy PostgreSQL as part of this chart + enabled: false + + # External PostgreSQL connection details + # If postgresql.enabled is false, provide external database details + external: + host: "postgresql" + port: 5432 + database: "memora" + username: "memora" + # Password should be provided via secret + # password: "" + +# Database URL (auto-generated from postgresql config if not provided) +# databaseUrl: "postgresql://user:pass@host:5432/database" + +# Ingress configuration +ingress: + enabled: false + className: "nginx" + annotations: {} + # cert-manager.io/cluster-issuer: "letsencrypt-prod" + # nginx.ingress.kubernetes.io/ssl-redirect: "true" + + hosts: + - host: memora.example.com + paths: + - path: / + pathType: Prefix + service: controlPlane + - path: /api + pathType: Prefix + service: api + + tls: [] + # - secretName: memora-tls + # hosts: + # - memora.example.com + +# Service Account +serviceAccount: + create: true + annotations: {} + name: "" + +# Pod annotations +podAnnotations: {} + +# Pod security context +podSecurityContext: + fsGroup: 1000 + +# Security context +securityContext: + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + +# Node selector +nodeSelector: {} + +# Tolerations +tolerations: [] + +# Affinity +affinity: {} + +# Autoscaling +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 diff --git a/local-db/.gitignore b/local-db/.gitignore deleted file mode 100644 index 55d7288c..00000000 --- a/local-db/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# Environment files -.env -.env.local - -# Docker volumes (data persistence) -postgres_data/ diff --git a/local-db/docker-compose.yml b/local-db/docker-compose.yml deleted file mode 100644 index ace0051b..00000000 --- a/local-db/docker-compose.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: '3.8' - -services: - postgres: - image: pgvector/pgvector:pg16 - container_name: memora-postgres - environment: - POSTGRES_USER: memora - POSTGRES_PASSWORD: memora_dev - POSTGRES_DB: memora - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - - ./init-extensions.sql:/docker-entrypoint-initdb.d/01-init-extensions.sql - healthcheck: - test: ["CMD-SHELL", "pg_isready -U memora"] - interval: 5s - timeout: 5s - retries: 5 - -volumes: - postgres_data: diff --git a/local-db/init-extensions.sql b/local-db/init-extensions.sql deleted file mode 100644 index ad7b5caa..00000000 --- a/local-db/init-extensions.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Enable pgvector extension -CREATE EXTENSION IF NOT EXISTS vector; diff --git a/memora-cli/.github/workflows/release.yml b/memora-cli/.github/workflows/release.yml new file mode 100644 index 00000000..d02eadb1 --- /dev/null +++ b/memora-cli/.github/workflows/release.yml @@ -0,0 +1,102 @@ +name: Release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + build: + name: Build ${{ matrix.target }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + artifact_name: memora + release_name: memora-linux-x86_64 + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu + artifact_name: memora + release_name: memora-linux-arm64 + - os: macos-latest + target: x86_64-apple-darwin + artifact_name: memora + release_name: memora-macos-x86_64 + - os: macos-latest + target: aarch64-apple-darwin + artifact_name: memora + release_name: memora-macos-arm64 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Install cross-compilation tools (Linux ARM64) + if: matrix.target == 'aarch64-unknown-linux-gnu' + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu + + - name: Build + run: cargo build --release --target ${{ matrix.target }} + + - name: Strip binary (Linux) + if: runner.os == 'Linux' + run: strip target/${{ matrix.target }}/release/${{ matrix.artifact_name }} + + - name: Strip binary (macOS) + if: runner.os == 'macOS' + run: strip target/${{ matrix.target }}/release/${{ matrix.artifact_name }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.release_name }} + path: target/${{ matrix.target }}/release/${{ matrix.artifact_name }} + + release: + name: Create Release + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Create checksums + run: | + cd artifacts + for dir in */; do + cd "$dir" + sha256sum * > SHA256SUMS + cd .. + done + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: | + artifacts/memora-linux-x86_64/memora + artifacts/memora-linux-arm64/memora + artifacts/memora-macos-x86_64/memora + artifacts/memora-macos-arm64/memora + artifacts/*/SHA256SUMS + draft: false + prerelease: false + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/memora-cli/.gitignore b/memora-cli/.gitignore new file mode 100644 index 00000000..c7377e76 --- /dev/null +++ b/memora-cli/.gitignore @@ -0,0 +1,20 @@ +# Rust +/target/ +Cargo.lock + +# Distribution +/dist/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Backup files +*.bak diff --git a/memora-cli/Cargo.toml b/memora-cli/Cargo.toml new file mode 100644 index 00000000..273aa672 --- /dev/null +++ b/memora-cli/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "memora-cli-rust" +version = "0.1.0" +edition = "2021" +authors = ["Memora Team"] +description = "A beautiful CLI for Memora - semantic memory system" +license = "MIT" + +[[bin]] +name = "memora" +path = "src/main.rs" + +[dependencies] +# CLI framework +clap = { version = "4.5", features = ["derive", "env"] } + +# HTTP client +reqwest = { version = "0.12", features = ["json", "blocking"] } +tokio = { version = "1", features = ["full"] } + +# Serialization +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_yaml = "0.9" + +# TUI libraries +ratatui = "0.29" +crossterm = "0.28" + +# Colors and styling +colored = "2.1" +indicatif = "0.17" + +# Error handling +anyhow = "1.0" +thiserror = "1.0" + +# Utilities +chrono = "0.4" +walkdir = "2.5" + +[profile.release] +opt-level = "z" +lto = true +codegen-units = 1 +panic = "abort" +strip = true diff --git a/memora-cli/README.md b/memora-cli/README.md deleted file mode 100644 index 250ccb69..00000000 --- a/memora-cli/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# Memora CLI - -Modern command-line interface for the Memora Temporal Semantic Memory System. - -## Installation - -```bash -pip install memora-cli -``` - -## Configuration - -Set the API endpoint URL (defaults to `http://localhost:8080`): - -```bash -export MEMORA_API_URL="http://localhost:8080" -``` - -## Commands - -### Search Memories - -```bash -memora search alice "What did she say about AI?" -memora search alice "hiking activities" --type world --max-tokens 8000 -memora search alice "recent events" --budget 150 --trace -``` - -### Think (Generate Answers) - -```bash -memora think alice "What do you think about machine learning?" -``` - -### Store Memories - -Store a single memory: - -```bash -memora put alice "Alice loves machine learning and AI" -memora put alice "Today we discussed neural networks" --context "team meeting" - -# Async mode - returns immediately, processes in background -memora put alice "Important note" --async -``` - -### Import Files - -Import memories from local files (.txt and .md): - -```bash -# Import a single file -memora put-files alice meeting-notes.txt - -# Import all files from a directory -memora put-files alice ./documents/ - -# Async mode - queue files for background processing -memora put-files alice ./documents/ --async -``` - -### List Agents - -```bash -memora agents -``` - -## Features - -- Beautiful TUI with Rich formatting (panels, tables, syntax highlighting) -- Color-coded fact types (cyan=world, magenta=agent, yellow=opinion) -- Progress bars and spinners for async operations -- Tree views for file hierarchies -- HTTP client (no direct database access needed) - -## Requirements - -- Python >= 3.11 -- Memora API server running diff --git a/memora-cli/build.sh b/memora-cli/build.sh new file mode 100755 index 00000000..65928360 --- /dev/null +++ b/memora-cli/build.sh @@ -0,0 +1,100 @@ +#!/bin/bash +set -e + +# Build script for memora-cli-rust +# This script builds optimized binaries for multiple platforms + +# Source cargo environment if it exists +if [ -f "$HOME/.cargo/env" ]; then + source "$HOME/.cargo/env" +fi + +# Add cargo to PATH if not already there +export PATH="$HOME/.cargo/bin:$PATH" + +# Check if cargo is available +if ! command -v cargo &> /dev/null; then + echo "Error: Cargo not found. Please install Rust first:" + echo " curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh" + exit 1 +fi + +echo "Building Memora CLI for multiple platforms..." + +# Ensure we're in the right directory +cd "$(dirname "$0")" + +# Create dist directory if it doesn't exist +mkdir -p dist + +# Get version from Cargo.toml +VERSION=$(grep '^version' Cargo.toml | head -1 | cut -d'"' -f2) +echo "Version: $VERSION" + +# Function to build for a target +build_target() { + local target=$1 + local output_name=$2 + + echo "" + echo "Building for $target..." + + # Check if target is installed, install if not + if ! rustup target list | grep -q "$target (installed)"; then + echo "Installing target $target..." + rustup target add "$target" + fi + + # Build + cargo build --release --target "$target" + + # Copy to dist + if [[ "$target" == *"windows"* ]]; then + cp "target/$target/release/memora.exe" "dist/$output_name.exe" + echo "Created: dist/$output_name.exe" + else + cp "target/$target/release/memora" "dist/$output_name" + chmod +x "dist/$output_name" + echo "Created: dist/$output_name" + fi +} + +# Detect current platform +OS=$(uname -s) +ARCH=$(uname -m) + +echo "Detected platform: $OS $ARCH" + +# Build for current platform +case "$OS" in + Darwin) + if [[ "$ARCH" == "arm64" ]]; then + echo "Building for macOS ARM64 (Apple Silicon)..." + build_target "aarch64-apple-darwin" "memora-macos-arm64" + else + echo "Building for macOS x86_64 (Intel)..." + build_target "x86_64-apple-darwin" "memora-macos-x86_64" + fi + ;; + Linux) + if [[ "$ARCH" == "x86_64" ]]; then + echo "Building for Linux x86_64..." + build_target "x86_64-unknown-linux-gnu" "memora-linux-x86_64" + elif [[ "$ARCH" == "aarch64" ]]; then + echo "Building for Linux ARM64..." + build_target "aarch64-unknown-linux-gnu" "memora-linux-arm64" + fi + ;; + *) + echo "Unsupported OS: $OS" + exit 1 + ;; +esac + +echo "" +echo "Build complete! Binaries are in the dist/ directory:" +ls -lh dist/ + +echo "" +echo "To build for other platforms, run:" +echo " ./build.sh --all" diff --git a/memora-cli/install.sh b/memora-cli/install.sh new file mode 100755 index 00000000..0faeb08e --- /dev/null +++ b/memora-cli/install.sh @@ -0,0 +1,158 @@ +#!/bin/bash +set -e + +# Memora CLI installer +# Usage: curl -sSf https://your-domain.com/install.sh | sh + +REPO_URL="https://github.com/your-org/memora-cli" +INSTALL_DIR="${MEMORA_INSTALL_DIR:-$HOME/.local/bin}" +BINARY_NAME="memora" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_info() { + echo -e "${BLUE}ℹ${NC} $1" +} + +print_success() { + echo -e "${GREEN}✓${NC} $1" +} + +print_error() { + echo -e "${RED}✗${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}⚠${NC} $1" +} + +print_banner() { + echo "" + echo -e "${BLUE}╔══════════════════════════════════════════════════╗${NC}" + echo -e "${BLUE}║ MEMORA CLI INSTALLER ║${NC}" + echo -e "${BLUE}╚══════════════════════════════════════════════════╝${NC}" + echo "" +} + +# Detect platform +detect_platform() { + local os=$(uname -s) + local arch=$(uname -m) + + case "$os" in + Darwin) + if [[ "$arch" == "arm64" ]] || [[ "$arch" == "aarch64" ]]; then + echo "macos-arm64" + elif [[ "$arch" == "x86_64" ]]; then + echo "macos-x86_64" + else + print_error "Unsupported macOS architecture: $arch" + exit 1 + fi + ;; + Linux) + if [[ "$arch" == "x86_64" ]]; then + echo "linux-x86_64" + elif [[ "$arch" == "aarch64" ]] || [[ "$arch" == "arm64" ]]; then + echo "linux-arm64" + else + print_error "Unsupported Linux architecture: $arch" + exit 1 + fi + ;; + *) + print_error "Unsupported operating system: $os" + exit 1 + ;; + esac +} + +# Download binary +download_binary() { + local platform=$1 + local download_url="${REPO_URL}/releases/latest/download/memora-${platform}" + local tmp_file="/tmp/memora-$$" + + print_info "Downloading Memora CLI for $platform..." + + if command -v curl > /dev/null 2>&1; then + curl -fsSL "$download_url" -o "$tmp_file" + elif command -v wget > /dev/null 2>&1; then + wget -q "$download_url" -O "$tmp_file" + else + print_error "Neither curl nor wget found. Please install one of them." + exit 1 + fi + + echo "$tmp_file" +} + +# Install binary +install_binary() { + local tmp_file=$1 + + # Create install directory if it doesn't exist + mkdir -p "$INSTALL_DIR" + + # Move binary to install directory + mv "$tmp_file" "$INSTALL_DIR/$BINARY_NAME" + chmod +x "$INSTALL_DIR/$BINARY_NAME" + + print_success "Installed to: $INSTALL_DIR/$BINARY_NAME" +} + +# Check if directory is in PATH +check_path() { + if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then + print_warning "$INSTALL_DIR is not in your PATH" + echo "" + echo "Add it to your PATH by adding this line to your shell profile:" + echo "" + + # Detect shell + if [[ -n "$BASH_VERSION" ]]; then + echo " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.bashrc" + echo " source ~/.bashrc" + elif [[ -n "$ZSH_VERSION" ]]; then + echo " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.zshrc" + echo " source ~/.zshrc" + else + echo " export PATH=\"$INSTALL_DIR:\$PATH\"" + fi + echo "" + fi +} + +# Main installation flow +main() { + print_banner + + # Detect platform + platform=$(detect_platform) + print_info "Detected platform: $platform" + + # Download binary + tmp_file=$(download_binary "$platform") + + # Install binary + install_binary "$tmp_file" + + # Check PATH + check_path + + print_success "Installation complete!" + echo "" + print_info "Try it out: $BINARY_NAME --help" + echo "" + print_info "Configure the API URL:" + echo " export MEMORA_API_URL=http://localhost:8080" + echo "" +} + +# Run installation +main diff --git a/memora-cli/memora_cli/__init__.py b/memora-cli/memora_cli/__init__.py deleted file mode 100644 index fcc91240..00000000 --- a/memora-cli/memora_cli/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -Memora CLI - Modern command-line interface for the Memora Memory System. -""" - -__version__ = "0.1.0" diff --git a/memora-cli/memora_cli/main.py b/memora-cli/memora_cli/main.py deleted file mode 100644 index 08dac3d2..00000000 --- a/memora-cli/memora_cli/main.py +++ /dev/null @@ -1,565 +0,0 @@ -""" -Memora CLI - HTTP client for Memora API. -""" - -import os -from pathlib import Path -from typing import Optional, List -from datetime import datetime - -import typer -import httpx -from rich.console import Console -from rich.table import Table -from rich.panel import Panel -from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn -from rich.markdown import Markdown -from rich import box -from rich.tree import Tree - -app = typer.Typer( - name="memora", - help="Modern CLI for Memora - Temporal Semantic Memory System", - add_completion=False, -) - -console = Console() - - -def get_api_url(): - """Get API URL from environment variable.""" - api_url = os.getenv("MEMORA_API_URL", "http://localhost:8080") - return api_url.rstrip("/") - - -def make_api_request( - method: str, - endpoint: str, - json_data: Optional[dict] = None, - timeout: float = 60.0, -) -> dict: - """ - Make an API request with proper error handling. - - Args: - method: HTTP method (GET, POST, etc.) - endpoint: API endpoint path (e.g., "/api/search") - json_data: Optional JSON payload for POST requests - timeout: Request timeout in seconds - - Returns: - Response data as dict - - Raises: - typer.Exit on any error - """ - api_url = get_api_url() - full_url = f"{api_url}{endpoint}" - - try: - with httpx.Client(timeout=timeout) as client: - if method.upper() == "GET": - response = client.get(full_url) - elif method.upper() == "POST": - response = client.post(full_url, json=json_data) - else: - console.print(f"[red]Error: Unsupported HTTP method: {method}[/red]") - raise typer.Exit(1) - - # Check HTTP status - response.raise_for_status() - - # Parse response - data = response.json() - - # Check for success field in response (if present) - if "success" in data and not data["success"]: - error_msg = data.get("message", "Unknown error") - console.print(f"[red]API Error: {error_msg}[/red]") - if "detail" in data: - console.print(f"[yellow]Details: {data['detail']}[/yellow]") - raise typer.Exit(1) - - return data - - except httpx.HTTPStatusError as e: - console.print(f"[red]HTTP Error {e.response.status_code}[/red]") - try: - error_data = e.response.json() - if "detail" in error_data: - console.print(f"[red]Error: {error_data['detail']}[/red]") - else: - console.print(f"[red]Error: {error_data}[/red]") - except Exception: - console.print(f"[red]Error: {e.response.text}[/red]") - console.print(f"[yellow]Make sure the API is running at {api_url}[/yellow]") - raise typer.Exit(1) - except httpx.ConnectError as e: - console.print(f"[red]Connection Error: Failed to connect to API at {api_url}[/red]") - console.print(f"[yellow]Make sure the API server is running[/yellow]") - raise typer.Exit(1) - except httpx.TimeoutException: - console.print(f"[red]Timeout Error: Request took too long[/red]") - console.print(f"[yellow]Try increasing the timeout or check the API server[/yellow]") - raise typer.Exit(1) - except Exception as e: - console.print(f"[red]Unexpected Error: {e}[/red]") - raise typer.Exit(1) - - -@app.command() -def search( - agent_id: str = typer.Argument(..., help="Agent ID to search for"), - query: str = typer.Argument(..., help="Search query"), - fact_type: List[str] = typer.Option( - ["world", "agent", "opinion"], - "--type", - "-t", - help="Fact types to search (world/agent/opinion)", - ), - thinking_budget: int = typer.Option( - 100, "--budget", "-b", help="Thinking budget for search" - ), - max_tokens: int = typer.Option( - 4096, "--max-tokens", help="Maximum tokens for search results" - ), - trace: bool = typer.Option(False, "--trace", help="Show trace information"), -): - """ - Search memory using semantic similarity. - - Example: - memora search alice "What did she say about AI?" - """ - with console.status(f"[bold blue]Searching memories for {agent_id}...", spinner="dots"): - data = make_api_request( - method="POST", - endpoint="/api/search", - json_data={ - "query": query, - "fact_type": list(fact_type), - "agent_id": agent_id, - "thinking_budget": thinking_budget, - "max_tokens": max_tokens, - "trace": trace, - }, - timeout=60.0, - ) - - results = data.get("results", []) - trace_data = data.get("trace") - - # Display results - if not results: - console.print("[yellow]No results found.[/yellow]") - return - - console.print(f"\n[bold green]Found {len(results)} results:[/bold green]\n") - - for i, result in enumerate(results, 1): - # Create a panel for each result - score = result.get("score", 0.0) - text = result.get("text", "") - fact_type_val = result.get("fact_type", "unknown") - context = result.get("context", "") - date = result.get("date", "") - - # Color code based on fact type - type_colors = { - "world": "cyan", - "agent": "magenta", - "opinion": "yellow" - } - color = type_colors.get(fact_type_val, "white") - - # Build info line - info_parts = [f"[{color}]{fact_type_val.upper()}[/{color}]"] - if context: - info_parts.append(f"Context: {context}") - if date: - info_parts.append(f"Date: {date}") - info_parts.append(f"Score: {score:.3f}") - - info_line = " | ".join(info_parts) - - panel = Panel( - f"{text}\n\n[dim]{info_line}[/dim]", - title=f"[bold]Result {i}[/bold]", - border_style=color, - box=box.ROUNDED, - ) - console.print(panel) - - # Show trace if requested - if trace and trace_data: - console.print("\n[bold blue]Trace Information:[/bold blue]") - - trace_table = Table(show_header=True, box=box.SIMPLE) - trace_table.add_column("Metric", style="cyan") - trace_table.add_column("Value", style="green") - - if "search_time_seconds" in trace_data: - trace_table.add_row("Search Time", f"{trace_data['search_time_seconds']:.3f}s") - if "total_activated" in trace_data: - trace_table.add_row("Total Activated", str(trace_data["total_activated"])) - if "results_returned" in trace_data: - trace_table.add_row("Results Returned", str(trace_data["results_returned"])) - - console.print(trace_table) - - -@app.command() -def think( - agent_id: str = typer.Argument(..., help="Agent ID"), - query: str = typer.Argument(..., help="Question to think about"), - thinking_budget: int = typer.Option( - 50, "--budget", "-b", help="Thinking budget" - ), -): - """ - Think and generate an answer using agent identity and memories. - - Example: - memora think alice "What do you think about machine learning?" - """ - with console.status(f"[bold blue]Thinking...", spinner="dots"): - result = make_api_request( - method="POST", - endpoint="/api/think", - json_data={ - "query": query, - "agent_id": agent_id, - "thinking_budget": thinking_budget, - }, - timeout=60.0, - ) - - # Display answer - console.print(Panel( - Markdown(result["text"]), - title=f"[bold cyan]Answer for {agent_id}[/bold cyan]", - border_style="cyan", - box=box.DOUBLE, - )) - - # Display what the answer was based on - based_on = result.get("based_on", {}) - if based_on: - console.print("\n[bold blue]Based on:[/bold blue]\n") - - for fact_type, facts in based_on.items(): - if facts: - type_colors = { - "world": "cyan", - "agent": "magenta", - "opinion": "yellow" - } - color = type_colors.get(fact_type, "white") - - table = Table( - title=f"[{color}]{fact_type.upper()}[/{color}]", - show_header=True, - box=box.ROUNDED, - border_style=color, - ) - table.add_column("Text", style="white", width=80) - table.add_column("Score", justify="right", style="green", width=10) - - for fact in facts[:5]: # Show top 5 - text = fact.get("text", "") - score = fact.get("score", 0.0) - table.add_row(text, f"{score:.3f}") - - console.print(table) - - # Display new opinions formed - new_opinions = result.get("new_opinions", []) - if new_opinions: - console.print("\n[bold yellow]New Opinions Formed:[/bold yellow]\n") - for opinion in new_opinions: - console.print(Panel( - f"{opinion['text']}\n\n[dim]Confidence: {opinion['confidence']:.2f}[/dim]", - border_style="yellow", - box=box.ROUNDED, - )) - - -@app.command() -def put( - agent_id: str = typer.Argument(..., help="Agent ID"), - content: str = typer.Argument(..., help="Memory content to store"), - document_id: Optional[str] = typer.Option( - None, "--doc-id", "-d", help="Document ID (auto-generated if not provided)" - ), - context: Optional[str] = typer.Option( - None, "--context", "-c", help="Context for the memory" - ), - use_async: bool = typer.Option( - False, "--async", help="Use async batch put (returns immediately, processes in background)" - ), -): - """ - Store a memory from text input. - - Example: - memora put alice "Alice loves machine learning and AI" - memora put alice "Today we discussed neural networks" --context "team meeting" - memora put alice "Important note" --async - """ - # Generate document_id if not provided - if not document_id: - document_id = f"cli_put_{datetime.now().strftime('%Y%m%d_%H%M%S')}" - - # Prepare content - item = {"content": content} - if context: - item["context"] = context - - # Choose endpoint based on async flag - endpoint = "/api/memories/batch_async" if use_async else "/api/memories/batch" - status_msg = "Queueing memory" if use_async else "Storing memory" - - with Progress( - SpinnerColumn(), - TextColumn("[progress.description]{task.description}"), - BarColumn(), - TaskProgressColumn(), - console=console, - ) as progress: - task = progress.add_task(f"[cyan]{status_msg} for {agent_id}...", total=None) - - result = make_api_request( - method="POST", - endpoint=endpoint, - json_data={ - "agent_id": agent_id, - "items": [item], - "document_id": document_id, - }, - timeout=120.0, - ) - progress.update(task, completed=True) - - # Check if the result indicates success - if not result.get("success", False): - console.print(Panel( - f"[red]✗[/red] Failed to store memory\n" - f"[dim]Error:[/dim] {result.get('message', 'Unknown error')}", - title="[bold red]Storage Failed[/bold red]", - border_style="red", - box=box.ROUNDED, - )) - raise typer.Exit(1) - - # Display result based on async vs sync - if use_async and result.get("queued", False): - console.print(Panel( - f"[green]✓[/green] Memory queued for background processing\n" - f"[dim]Agent ID:[/dim] {agent_id}\n" - f"[dim]Document ID:[/dim] {document_id}\n" - f"[dim]Content length:[/dim] {len(content)} characters\n" - f"[dim]Items queued:[/dim] {result.get('items_count', 1)}\n" - f"[yellow]Processing in background...[/yellow]", - title="[bold green]Memory Queued[/bold green]", - border_style="green", - box=box.ROUNDED, - )) - else: - console.print(Panel( - f"[green]✓[/green] Successfully stored memory\n" - f"[dim]Agent ID:[/dim] {agent_id}\n" - f"[dim]Document ID:[/dim] {document_id}\n" - f"[dim]Content length:[/dim] {len(content)} characters\n" - f"[dim]Items processed:[/dim] {result.get('items_count', 1)}", - title="[bold green]Memory Stored[/bold green]", - border_style="green", - box=box.ROUNDED, - )) - - -@app.command(name="put-files") -def put_files( - agent_id: str = typer.Argument(..., help="Agent ID"), - path: str = typer.Argument(..., help="File or directory path"), - recursive: bool = typer.Option( - True, "--recursive/--no-recursive", "-r", help="Search directories recursively" - ), - use_async: bool = typer.Option( - False, "--async", help="Use async batch put (returns immediately, processes in background)" - ), -): - """ - Store memories from local files (.txt and .md only). - Each file becomes a separate document with the filename as doc_id. - - Example: - memora put-files alice ./documents/ - memora put-files alice meeting-notes.txt - memora put-files alice ./documents/ --async - """ - path_obj = Path(path) - - if not path_obj.exists(): - console.print(f"[red]Error: Path '{path}' does not exist[/red]") - raise typer.Exit(1) - - # Collect files to process - files_to_process = [] - - if path_obj.is_file(): - if path_obj.suffix.lower() in ['.txt', '.md']: - files_to_process.append(path_obj) - else: - console.print(f"[yellow]Warning: Skipping '{path}' - only .txt and .md files are supported[/yellow]") - raise typer.Exit(0) - else: - # Directory - find all .txt and .md files - pattern = "**/*" if recursive else "*" - for ext in ['.txt', '.md']: - files_to_process.extend(path_obj.glob(f"{pattern}{ext}")) - - if not files_to_process: - console.print(f"[yellow]No .txt or .md files found in '{path}'[/yellow]") - raise typer.Exit(0) - - # Display files to be processed - console.print(f"\n[bold]Found {len(files_to_process)} files to process:[/bold]\n") - - tree = Tree(f"[bold cyan]{path}[/bold cyan]") - for file_path in sorted(files_to_process): - size = file_path.stat().st_size - size_str = f"{size:,} bytes" if size < 1024 else f"{size/1024:.1f} KB" - tree.add(f"{file_path.name} [dim]({size_str})[/dim]") - console.print(tree) - console.print() - - # Process files - successful = 0 - failed = 0 - queued = 0 - - # Choose endpoint based on async flag - endpoint = "/api/memories/batch_async" if use_async else "/api/memories/batch" - status_msg = "Queueing files" if use_async else "Processing files" - - with Progress( - SpinnerColumn(), - TextColumn("[progress.description]{task.description}"), - BarColumn(), - TaskProgressColumn(), - console=console, - ) as progress: - main_task = progress.add_task( - f"[cyan]{status_msg} for {agent_id}...", - total=len(files_to_process) - ) - - for file_path in files_to_process: - try: - # Read file content - content = file_path.read_text(encoding='utf-8') - - # Use filename (without extension) as document_id - doc_id = file_path.stem - - # Prepare content - item = { - "content": content, - "context": f"File: {file_path.name}" - } - - # Store memory via API - result = make_api_request( - method="POST", - endpoint=endpoint, - json_data={ - "agent_id": agent_id, - "items": [item], - "document_id": doc_id, - }, - timeout=120.0, - ) - - # Check if the result indicates success - if not result.get("success", False): - raise Exception(result.get("message", "Unknown error")) - - if use_async and result.get("queued", False): - queued += 1 - else: - successful += 1 - progress.update(main_task, advance=1) - - except typer.Exit: - # Re-raise typer.Exit to stop execution - raise - except Exception as e: - console.print(f"[red]Failed to process {file_path.name}: {str(e)}[/red]") - failed += 1 - progress.update(main_task, advance=1) - - # Summary - console.print() - if use_async and queued > 0: - console.print(Panel( - f"[green]✓[/green] Successfully queued {queued} file(s) for background processing\n" - f"[red]✗[/red] Failed: {failed}\n" - f"[dim]Agent ID:[/dim] {agent_id}\n" - f"[yellow]Processing in background...[/yellow]", - title="[bold green]Files Queued[/bold green]", - border_style="green" if failed == 0 else "yellow", - box=box.ROUNDED, - )) - elif successful > 0: - console.print(Panel( - f"[green]✓[/green] Successfully processed {successful} file(s)\n" - f"[red]✗[/red] Failed: {failed}\n" - f"[dim]Agent ID:[/dim] {agent_id}", - title="[bold green]Files Processed[/bold green]", - border_style="green" if failed == 0 else "yellow", - box=box.ROUNDED, - )) - else: - console.print("[red]No files were successfully processed[/red]") - - -@app.command() -def agents(): - """ - List all agents in the memory system. - - Example: - memora agents - """ - with console.status("[bold blue]Fetching agents...", spinner="dots"): - data = make_api_request( - method="GET", - endpoint="/api/agents", - timeout=30.0, - ) - - agent_list = data.get("agents", []) - - if not agent_list: - console.print("[yellow]No agents found in the system.[/yellow]") - return - - console.print(f"\n[bold green]Found {len(agent_list)} agent(s):[/bold green]\n") - - table = Table(show_header=True, box=box.ROUNDED, border_style="cyan") - table.add_column("#", style="dim", width=6) - table.add_column("Agent ID", style="cyan") - - for i, agent in enumerate(agent_list, 1): - table.add_row(str(i), agent) - - console.print(table) - - -def main(): - """Main entry point for the CLI.""" - app() - - -if __name__ == "__main__": - main() diff --git a/memora-cli/pyproject.toml b/memora-cli/pyproject.toml deleted file mode 100644 index 09020b64..00000000 --- a/memora-cli/pyproject.toml +++ /dev/null @@ -1,21 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "memora-cli" -version = "0.1.0" -description = "Modern CLI for Memora - Temporal Semantic Memory System" -readme = "README.md" -requires-python = ">=3.11" -dependencies = [ - "rich>=13.0.0", - "typer>=0.20.0", - "httpx>=0.27.0", -] - -[project.scripts] -memora = "memora_cli.main:main" - -[tool.hatch.build.targets.wheel] -packages = ["memora_cli"] diff --git a/memora-cli/src/api.rs b/memora-cli/src/api.rs new file mode 100644 index 00000000..6ae90038 --- /dev/null +++ b/memora-cli/src/api.rs @@ -0,0 +1,279 @@ +use anyhow::{Context, Result}; +use reqwest::blocking::{Client, Response}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +pub struct ApiError { + pub url: String, + pub request_body: String, + pub response_status: Option, + pub response_body: Option, + pub error: anyhow::Error, +} + +#[derive(Debug, Serialize)] +pub struct SearchRequest { + pub query: String, + pub fact_type: Vec, + pub agent_id: String, + pub thinking_budget: i32, + pub max_tokens: i32, + pub trace: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SearchResponse { + pub results: Vec, + pub trace: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Fact { + #[serde(default)] + pub id: Option, + pub text: String, + #[serde(rename = "type", default)] + pub fact_type: Option, + pub activation: Option, + #[serde(default)] + pub context: Option, + #[serde(default)] + pub event_date: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct TraceInfo { + pub total_time: Option, + pub activation_count: Option, +} + +#[derive(Debug, Serialize)] +pub struct ThinkRequest { + pub query: String, + pub agent_id: String, + pub thinking_budget: i32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ThinkResponse { + pub text: String, + pub based_on: Vec, + pub new_opinions: Vec, +} + +#[derive(Debug, Serialize)] +pub struct MemoryItem { + pub content: String, + pub context: Option, +} + +#[derive(Debug, Serialize)] +pub struct BatchMemoryRequest { + pub agent_id: String, + pub items: Vec, + pub document_id: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct BatchMemoryResponse { + pub success: bool, + pub stored_count: Option, + pub error: Option, + pub job_id: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum AgentsResponse { + Success { + agents: Vec, + }, + Error { + error: String, + }, +} + +#[derive(Debug, Serialize)] +pub struct Agent { + pub agent_id: String, +} + +pub struct ApiClient { + client: Client, + base_url: String, +} + +impl ApiClient { + pub fn new(base_url: String) -> Result { + let client = Client::builder() + .timeout(Duration::from_secs(60)) + .build() + .context("Failed to create HTTP client")?; + + Ok(ApiClient { client, base_url }) + } + + pub fn search(&self, request: SearchRequest, verbose: bool) -> Result { + let url = format!("{}/api/search", self.base_url); + let request_body = serde_json::to_string_pretty(&request).unwrap_or_default(); + + if verbose { + eprintln!("Request URL: {}", url); + eprintln!("Request body:\n{}", request_body); + } + + let response = self + .client + .post(&url) + .json(&request) + .timeout(Duration::from_secs(120)) + .send()?; + + let status = response.status(); + if verbose { + eprintln!("Response status: {}", status); + } + + if !status.is_success() { + let error_body = response.text().unwrap_or_default(); + if verbose { + eprintln!("Error response body:\n{}", error_body); + } + anyhow::bail!("API returned error status {}: {}", status, error_body); + } + + let response_text = response.text()?; + if verbose { + eprintln!("Response body:\n{}", response_text); + } + + let result: SearchResponse = serde_json::from_str(&response_text) + .with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?; + Ok(result) + } + + pub fn think(&self, request: ThinkRequest, verbose: bool) -> Result { + let url = format!("{}/api/think", self.base_url); + + if verbose { + eprintln!("Request URL: {}", url); + eprintln!("Request body:\n{}", serde_json::to_string_pretty(&request).unwrap_or_default()); + } + + let response = self + .client + .post(&url) + .json(&request) + .timeout(Duration::from_secs(120)) + .send()?; + + let status = response.status(); + if verbose { + eprintln!("Response status: {}", status); + } + + if !status.is_success() { + let error_body = response.text().unwrap_or_default(); + if verbose { + eprintln!("Error response body:\n{}", error_body); + } + anyhow::bail!("API returned error status {}: {}", status, error_body); + } + + let response_text = response.text()?; + if verbose { + eprintln!("Response body:\n{}", response_text); + } + + let result: ThinkResponse = serde_json::from_str(&response_text) + .with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?; + Ok(result) + } + + pub fn put_memories(&self, request: BatchMemoryRequest, async_mode: bool, verbose: bool) -> Result { + let endpoint = if async_mode { + "batch_async" + } else { + "batch" + }; + let url = format!("{}/api/memories/{}", self.base_url, endpoint); + + if verbose { + eprintln!("Request URL: {}", url); + eprintln!("Request body:\n{}", serde_json::to_string_pretty(&request).unwrap_or_default()); + } + + let response = self + .client + .post(&url) + .json(&request) + .timeout(Duration::from_secs(120)) + .send()?; + + let status = response.status(); + if verbose { + eprintln!("Response status: {}", status); + } + + if !status.is_success() { + let error_body = response.text().unwrap_or_default(); + if verbose { + eprintln!("Error response body:\n{}", error_body); + } + anyhow::bail!("API returned error status {}: {}", status, error_body); + } + + let response_text = response.text()?; + if verbose { + eprintln!("Response body:\n{}", response_text); + } + + let result: BatchMemoryResponse = serde_json::from_str(&response_text) + .with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?; + Ok(result) + } + + pub fn list_agents(&self, verbose: bool) -> Result> { + let url = format!("{}/api/agents", self.base_url); + + if verbose { + eprintln!("Request URL: {}", url); + } + + let response = self + .client + .get(&url) + .timeout(Duration::from_secs(30)) + .send()?; + + let status = response.status(); + if verbose { + eprintln!("Response status: {}", status); + } + + if !status.is_success() { + let error_body = response.text().unwrap_or_default(); + if verbose { + eprintln!("Error response body:\n{}", error_body); + } + anyhow::bail!("API returned error status {}: {}", status, error_body); + } + + let response_text = response.text()?; + if verbose { + eprintln!("Response body:\n{}", response_text); + } + + let result: AgentsResponse = serde_json::from_str(&response_text) + .with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?; + + match result { + AgentsResponse::Success { agents } => { + Ok(agents.into_iter().map(|agent_id| Agent { agent_id }).collect()) + } + AgentsResponse::Error { error } => { + anyhow::bail!("Failed to list agents: {}", error) + } + } + } +} diff --git a/memora-cli/src/config.rs b/memora-cli/src/config.rs new file mode 100644 index 00000000..f66e2d18 --- /dev/null +++ b/memora-cli/src/config.rs @@ -0,0 +1,32 @@ +use anyhow::{Context, Result}; +use std::env; + +pub struct Config { + pub api_url: String, +} + +impl Config { + pub fn from_env() -> Result { + let api_url = env::var("MEMORA_API_URL") + .unwrap_or_else(|_| "http://localhost:8080".to_string()); + + // Validate URL format + if !api_url.starts_with("http://") && !api_url.starts_with("https://") { + anyhow::bail!( + "Invalid API URL: {}. Must start with http:// or https://", + api_url + ); + } + + Ok(Config { api_url }) + } + + pub fn api_url(&self) -> &str { + &self.api_url + } +} + +pub fn generate_doc_id() -> String { + let now = chrono::Local::now(); + format!("cli_put_{}", now.format("%Y%m%d_%H%M%S")) +} diff --git a/memora-cli/src/errors.rs b/memora-cli/src/errors.rs new file mode 100644 index 00000000..df74c58c --- /dev/null +++ b/memora-cli/src/errors.rs @@ -0,0 +1,180 @@ +use anyhow::Result; +use colored::*; + +pub fn handle_api_error(err: anyhow::Error, api_url: &str) -> ! { + eprintln!("{}", format_error_message(&err, api_url)); + std::process::exit(1); +} + +fn format_error_message(err: &anyhow::Error, api_url: &str) -> String { + let err_str = err.to_string(); + + // Connection refused + if err_str.contains("Connection refused") || err_str.contains("tcp connect error") || err_str.contains("error sending request") { + return format!( + "{} {}\n\n{}\n {}\n\n{}\n • {}\n • {}\n • {}\n\n{}\n {}", + "✗".bright_red().bold(), + "Cannot connect to Memora API".bright_red().bold(), + "API URL:".bright_yellow(), + api_url.bright_white(), + "Possible causes:".bright_yellow(), + "The Memora API server is not running".bright_white(), + format!("The server is running on a different address than {}", api_url).bright_white(), + "A firewall is blocking the connection".bright_white(), + "Try:".bright_green(), + "Start the Memora API server and ensure it's accessible".bright_white() + ); + } + + // Timeout + if err_str.contains("timeout") || err_str.contains("Timeout") { + return format!( + "{} {}\n\n{}\n {}\n\n{}\n • {}\n • {}\n\n{}\n • {}\n • {}", + "✗".bright_red().bold(), + "Request timed out".bright_red().bold(), + "API URL:".bright_yellow(), + api_url.bright_white(), + "Possible causes:".bright_yellow(), + "The API server is slow to respond".bright_white(), + "Network latency is too high".bright_white(), + "Try:".bright_green(), + "Check if the API server is healthy".bright_white(), + "Try again with a better network connection".bright_white() + ); + } + + // DNS/Host resolution + if err_str.contains("dns") || err_str.contains("DNS") || err_str.contains("failed to lookup") { + return format!( + "{} {}\n\n{}\n {}\n\n{}\n • {}\n • {}\n\n{}\n {}", + "✗".bright_red().bold(), + "Cannot resolve API hostname".bright_red().bold(), + "API URL:".bright_yellow(), + api_url.bright_white(), + "Possible causes:".bright_yellow(), + "The hostname in the API URL is incorrect".bright_white(), + "DNS server is not responding".bright_white(), + "Try:".bright_green(), + "Check the MEMORA_API_URL environment variable".bright_white() + ); + } + + // 404 Not Found + if err_str.contains("404") { + return format!( + "{} {}\n\n{}\n {}\n\n{}\n • {}\n • {}\n\n{}\n {}", + "✗".bright_red().bold(), + "API endpoint not found (404)".bright_red().bold(), + "API URL:".bright_yellow(), + api_url.bright_white(), + "Possible causes:".bright_yellow(), + "The API endpoint path has changed".bright_white(), + "You're using an incompatible API version".bright_white(), + "Try:".bright_green(), + "Check that you're using the correct Memora API version".bright_white() + ); + } + + // 401/403 Authentication + if err_str.contains("401") || err_str.contains("403") { + return format!( + "{} {}\n\n{}\n {}\n\n{}\n • {}\n • {}\n\n{}\n {}", + "✗".bright_red().bold(), + "Authentication failed".bright_red().bold(), + "API URL:".bright_yellow(), + api_url.bright_white(), + "Possible causes:".bright_yellow(), + "API requires authentication".bright_white(), + "Invalid or missing credentials".bright_white(), + "Try:".bright_green(), + "Check if the API requires an API key or token".bright_white() + ); + } + + // 500 Server Error + if err_str.contains("500") || err_str.contains("502") || err_str.contains("503") { + return format!( + "{} {}\n\n{}\n {}\n\n{}\n • {}\n • {}\n\n{}\n • {}\n • {}", + "✗".bright_red().bold(), + "API server error".bright_red().bold(), + "API URL:".bright_yellow(), + api_url.bright_white(), + "The server encountered an error:".bright_yellow(), + "Internal server error (500)".bright_white(), + "Service temporarily unavailable".bright_white(), + "Try:".bright_green(), + "Check the API server logs for details".bright_white(), + "Try again in a few moments".bright_white() + ); + } + + // Invalid URL + if err_str.contains("invalid URL") || err_str.contains("InvalidUri") { + return format!( + "{} {}\n\n{}\n {}\n\n{}\n {}\n\n{}\n {}", + "✗".bright_red().bold(), + "Invalid API URL".bright_red().bold(), + "API URL:".bright_yellow(), + api_url.bright_white(), + "The API URL format is invalid.".bright_yellow(), + "Ensure it starts with http:// or https://".bright_white(), + "Example:".bright_green(), + "export MEMORA_API_URL=http://localhost:8080".bright_white() + ); + } + + // JSON parsing error - show actual response + if err_str.contains("Failed to parse") || err_str.contains("error decoding") { + // Extract the actual response if available + let response_hint = if err_str.contains("Response was:") { + let parts: Vec<&str> = err_str.split("Response was:").collect(); + if parts.len() > 1 { + format!("\n{}\n{}", "Actual response:".bright_yellow(), parts[1].trim().bright_white()) + } else { + String::new() + } + } else { + String::new() + }; + + return format!( + "{} {}\n\n{}\n {}\n\n{}\n • {}\n • {}\n • {}{}\n\n{}\n • {}\n • {}", + "✗".bright_red().bold(), + "Invalid API response format".bright_red().bold(), + "API URL:".bright_yellow(), + api_url.bright_white(), + "Possible causes:".bright_yellow(), + "The API returned an unexpected response format".bright_white(), + "Version mismatch between CLI and API".bright_white(), + "The API endpoint doesn't exist or returned HTML instead of JSON".bright_white(), + response_hint, + "Try:".bright_green(), + "Run with --verbose flag to see the full request/response".bright_white(), + "Ensure you're using a compatible Memora API version".bright_white() + ); + } + + // Generic error with the full error message + format!( + "{} {}\n\n{}\n {}\n\n{}\n {}\n\n{}\n • {}\n • {}\n • {}", + "✗".bright_red().bold(), + "API request failed".bright_red().bold(), + "API URL:".bright_yellow(), + api_url.bright_white(), + "Error:".bright_yellow(), + err_str.bright_white(), + "Suggestions:".bright_green(), + "Check that MEMORA_API_URL is set correctly".bright_white(), + "Ensure the Memora API server is running".bright_white(), + "Verify network connectivity to the API server".bright_white() + ) +} + +pub fn print_config_help() { + println!("\n{}", "Configuration:".bright_cyan().bold()); + println!(" Set the API URL using an environment variable:"); + println!(" {}", "export MEMORA_API_URL=http://localhost:8080".bright_white()); + println!("\n Add to your shell profile to make it permanent:"); + println!(" {}", "echo 'export MEMORA_API_URL=http://localhost:8080' >> ~/.zshrc".bright_black()); + println!(); +} diff --git a/memora-cli/src/main.rs b/memora-cli/src/main.rs new file mode 100644 index 00000000..05733e0b --- /dev/null +++ b/memora-cli/src/main.rs @@ -0,0 +1,439 @@ +mod api; +mod config; +mod errors; +mod output; +mod ui; + +use anyhow::{Context, Result}; +use api::{ApiClient, BatchMemoryRequest, MemoryItem, SearchRequest, ThinkRequest}; +use clap::{Parser, Subcommand, ValueEnum}; +use config::Config; +use output::OutputFormat; +use std::fs; +use std::path::PathBuf; +use walkdir::WalkDir; + +#[derive(Debug, Clone, Copy, ValueEnum)] +enum Format { + Pretty, + Json, + Yaml, +} + +impl From for OutputFormat { + fn from(f: Format) -> Self { + match f { + Format::Pretty => OutputFormat::Pretty, + Format::Json => OutputFormat::Json, + Format::Yaml => OutputFormat::Yaml, + } + } +} + +#[derive(Parser)] +#[command(name = "memora")] +#[command(about = "Memora CLI - Semantic memory system", long_about = None)] +#[command(version)] +struct Cli { + /// Output format (pretty, json, yaml) + #[arg(short = 'o', long, global = true, default_value = "pretty")] + output: Format, + + /// Show verbose output including full requests and responses + #[arg(short = 'v', long, global = true)] + verbose: bool, + + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Search for memories using semantic search + Search { + /// Agent ID to search for + agent_id: String, + + /// Search query + query: String, + + /// Fact types to search (world, agent, opinion) + #[arg(short = 't', long, value_delimiter = ',', default_values = &["world", "agent", "opinion"])] + fact_type: Vec, + + /// Thinking budget for search + #[arg(short = 'b', long, default_value = "100")] + budget: i32, + + /// Maximum tokens for results + #[arg(long, default_value = "4096")] + max_tokens: i32, + + /// Show trace information (timing, activation counts) + #[arg(long)] + trace: bool, + }, + + /// Generate answers using agent identity and memories + Think { + /// Agent ID to think as + agent_id: String, + + /// Query to think about + query: String, + + /// Thinking budget + #[arg(short = 'b', long, default_value = "50")] + budget: i32, + }, + + /// Store a single memory + Put { + /// Agent ID to store memory for + agent_id: String, + + /// Memory content to store + content: String, + + /// Document ID (auto-generated if not provided) + #[arg(short = 'd', long)] + doc_id: Option, + + /// Context for the memory + #[arg(short = 'c', long)] + context: Option, + + /// Queue for background processing + #[arg(long)] + r#async: bool, + }, + + /// Bulk import memories from files + PutFiles { + /// Agent ID to store memories for + agent_id: String, + + /// Path to file or directory + path: PathBuf, + + /// Search directories recursively + #[arg(short = 'r', long, default_value = "true")] + recursive: bool, + + /// Queue for background processing + #[arg(long)] + r#async: bool, + }, + + /// List all agents + Agents, +} + +fn main() { + if let Err(e) = run() { + std::process::exit(1); + } +} + +fn run() -> Result<()> { + let cli = Cli::parse(); + + let output_format: OutputFormat = cli.output.into(); + let verbose = cli.verbose; + + // Load configuration + let config = Config::from_env().unwrap_or_else(|e| { + ui::print_error(&format!("Configuration error: {}", e)); + errors::print_config_help(); + std::process::exit(1); + }); + + let api_url = config.api_url().to_string(); + + // Create API client + let client = ApiClient::new(api_url.clone()).unwrap_or_else(|e| { + errors::handle_api_error(e, &api_url); + }); + + // Execute command and handle errors + let result: Result<()> = match cli.command { + Commands::Search { + agent_id, + query, + fact_type, + budget, + max_tokens, + trace, + } => { + let spinner = if output_format == OutputFormat::Pretty { + Some(ui::create_spinner("Searching memories...")) + } else { + None + }; + + let request = SearchRequest { + query, + fact_type, + agent_id, + thinking_budget: budget, + max_tokens, + trace, + }; + + let response = client.search(request, verbose); + + if let Some(sp) = spinner { + sp.finish_and_clear(); + } + + match response { + Ok(resp) => { + if output_format == OutputFormat::Pretty { + ui::print_search_results(&resp, trace); + } else { + output::print_output(&resp, output_format)?; + } + Ok(()) + } + Err(e) => Err(e) + } + } + + Commands::Think { + agent_id, + query, + budget, + } => { + let spinner = if output_format == OutputFormat::Pretty { + Some(ui::create_spinner("Thinking...")) + } else { + None + }; + + let request = ThinkRequest { + query, + agent_id, + thinking_budget: budget, + }; + + let response = client.think(request, verbose); + + if let Some(sp) = spinner { + sp.finish_and_clear(); + } + + match response { + Ok(resp) => { + if output_format == OutputFormat::Pretty { + ui::print_think_response(&resp); + } else { + output::print_output(&resp, output_format)?; + } + Ok(()) + } + Err(e) => Err(e) + } + } + + Commands::Put { + agent_id, + content, + doc_id, + context, + r#async, + } => { + let doc_id = doc_id.unwrap_or_else(config::generate_doc_id); + + let spinner = if output_format == OutputFormat::Pretty { + Some(ui::create_spinner("Storing memory...")) + } else { + None + }; + + let item = MemoryItem { + content: content.clone(), + context, + }; + + let request = BatchMemoryRequest { + agent_id, + items: vec![item], + document_id: Some(doc_id.clone()), + }; + + let response = client.put_memories(request, r#async, verbose); + + if let Some(sp) = spinner { + sp.finish_and_clear(); + } + + match response { + Ok(resp) => { + if output_format == OutputFormat::Pretty { + ui::print_stored_memory(&doc_id, &content, r#async); + if let Some(job_id) = resp.job_id { + ui::print_info(&format!("Job ID: {}", job_id)); + } + } else { + output::print_output(&resp, output_format)?; + } + Ok(()) + } + Err(e) => Err(e) + } + } + + Commands::PutFiles { + agent_id, + path, + recursive, + r#async, + } => { + if !path.exists() { + anyhow::bail!("Path does not exist: {}", path.display()); + } + + let mut files = Vec::new(); + + if path.is_file() { + files.push(path); + } else if path.is_dir() { + if recursive { + for entry in WalkDir::new(&path) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().is_file()) + { + let path = entry.path(); + if let Some(ext) = path.extension() { + if ext == "txt" || ext == "md" { + files.push(path.to_path_buf()); + } + } + } + } else { + for entry in fs::read_dir(&path)? { + let entry = entry?; + let path = entry.path(); + if path.is_file() { + if let Some(ext) = path.extension() { + if ext == "txt" || ext == "md" { + files.push(path); + } + } + } + } + } + } + + if files.is_empty() { + ui::print_warning("No .txt or .md files found"); + return Ok(()); + } + + ui::print_info(&format!("Found {} files to import", files.len())); + + let pb = ui::create_progress_bar(files.len() as u64, "Processing files"); + + let mut items = Vec::new(); + let mut document_id = None; + + for file_path in &files { + let content = fs::read_to_string(file_path) + .with_context(|| format!("Failed to read file: {}", file_path.display()))?; + + let doc_id = file_path + .file_stem() + .and_then(|s| s.to_str()) + .map(|s| s.to_string()) + .unwrap_or_else(config::generate_doc_id); + + // Use the first file's stem as the document_id for the batch + if document_id.is_none() { + document_id = Some(doc_id); + } + + items.push(MemoryItem { + content, + context: None, + }); + + pb.inc(1); + } + + pb.finish_with_message("Files processed"); + + let spinner = if output_format == OutputFormat::Pretty { + Some(ui::create_spinner("Uploading memories...")) + } else { + None + }; + + let request = BatchMemoryRequest { + agent_id, + items, + document_id, + }; + let response = client.put_memories(request, r#async, verbose); + + if let Some(sp) = spinner { + sp.finish_and_clear(); + } + + match response { + Ok(resp) => { + if output_format == OutputFormat::Pretty { + if r#async { + ui::print_success(&format!( + "Queued {} files for background processing", + files.len() + )); + if let Some(job_id) = resp.job_id { + ui::print_info(&format!("Job ID: {}", job_id)); + } + } else { + ui::print_success(&format!("Successfully stored {} memories", files.len())); + } + } else { + output::print_output(&resp, output_format)?; + } + Ok(()) + } + Err(e) => Err(e) + } + } + + Commands::Agents => { + let spinner = if output_format == OutputFormat::Pretty { + Some(ui::create_spinner("Fetching agents...")) + } else { + None + }; + + let response = client.list_agents(verbose); + + if let Some(sp) = spinner { + sp.finish_and_clear(); + } + + match response { + Ok(agents) => { + if output_format == OutputFormat::Pretty { + ui::print_agents_table(&agents); + } else { + output::print_output(&agents, output_format)?; + } + Ok(()) + } + Err(e) => Err(e) + } + } + }; + + // Handle API errors with nice messages + if let Err(e) = result { + errors::handle_api_error(e, &api_url); + } + + Ok(()) +} diff --git a/memora-cli/src/output.rs b/memora-cli/src/output.rs new file mode 100644 index 00000000..dedac86d --- /dev/null +++ b/memora-cli/src/output.rs @@ -0,0 +1,36 @@ +use anyhow::Result; +use serde::Serialize; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum OutputFormat { + Pretty, + Json, + Yaml, +} + +impl OutputFormat { + pub fn from_str(s: &str) -> Option { + match s.to_lowercase().as_str() { + "pretty" | "default" => Some(OutputFormat::Pretty), + "json" => Some(OutputFormat::Json), + "yaml" | "yml" => Some(OutputFormat::Yaml), + _ => None, + } + } +} + +pub fn print_output(data: &T, format: OutputFormat) -> Result<()> { + match format { + OutputFormat::Json => { + println!("{}", serde_json::to_string_pretty(data)?); + } + OutputFormat::Yaml => { + println!("{}", serde_yaml::to_string(data)?); + } + OutputFormat::Pretty => { + // This should not be called - pretty printing is handled in ui.rs + unreachable!("Pretty format should be handled separately") + } + } + Ok(()) +} diff --git a/memora-cli/src/ui.rs b/memora-cli/src/ui.rs new file mode 100644 index 00000000..a84ec50a --- /dev/null +++ b/memora-cli/src/ui.rs @@ -0,0 +1,217 @@ +use crate::api::{Agent, Fact, SearchResponse, ThinkResponse, TraceInfo}; +use colored::*; +use indicatif::{ProgressBar, ProgressStyle}; +use std::io::{self, Write}; + +pub fn print_banner() { + println!("{}", "╔══════════════════════════════════════════════════╗".bright_cyan()); + println!("{}", "║ MEMORA - Memory CLI ║".bright_cyan()); + println!("{}", "╚══════════════════════════════════════════════════╝".bright_cyan()); + println!(); +} + +pub fn print_section_header(title: &str) { + println!(); + println!("{}", format!("━━━ {} ━━━", title).bright_yellow().bold()); + println!(); +} + +pub fn print_fact(fact: &Fact, show_activation: bool) { + let fact_type = fact.fact_type.as_deref().unwrap_or("unknown"); + + let type_color = match fact_type { + "world" => "cyan", + "agent" => "magenta", + "opinion" => "yellow", + _ => "white", + }; + + let prefix = match fact_type { + "world" => "🌍", + "agent" => "🤖", + "opinion" => "💭", + _ => "📝", + }; + + print!("{} ", prefix); + print!("{}", format!("[{}]", fact_type.to_uppercase()).color(type_color).bold()); + + if show_activation { + if let Some(activation) = fact.activation { + print!(" {}", format!("({:.2})", activation).bright_black()); + } + } + + println!(); + println!(" {}", fact.text); + + // Show context if available + if let Some(context) = &fact.context { + println!(" {}: {}", "Context".bright_black(), context.bright_black()); + } + + // Show event date if available + if let Some(event_date) = &fact.event_date { + println!(" {}: {}", "Date".bright_black(), event_date.bright_black()); + } + + println!(); +} + +pub fn print_search_results(response: &SearchResponse, show_trace: bool) { + let results = &response.results; + print_section_header(&format!("Search Results ({})", results.len())); + + if results.is_empty() { + println!("{}", " No results found.".bright_black()); + } else { + for (i, fact) in results.iter().enumerate() { + println!("{}", format!(" Result #{}", i + 1).bright_black()); + print_fact(fact, true); + } + } + + if show_trace { + if let Some(trace) = &response.trace { + print_trace_info(trace); + } + } +} + +pub fn print_think_response(response: &ThinkResponse) { + print_section_header("Answer"); + println!("{}", response.text.bright_white()); + println!(); + + // Note: based_on facts are hidden in default output + // Use -o json to see the complete response including based_on facts + if !response.based_on.is_empty() { + println!(" {}", format!("(Based on {} facts - use -o json to see details)", response.based_on.len()).bright_black()); + println!(); + } + + if !response.new_opinions.is_empty() { + print_section_header(&format!("New opinions formed ({})", response.new_opinions.len())); + for opinion in &response.new_opinions { + println!(" 💭 {}", opinion.bright_yellow()); + } + println!(); + } +} + +pub fn print_trace_info(trace: &TraceInfo) { + print_section_header("Trace Information"); + + if let Some(time) = trace.total_time { + println!(" ⏱️ Total time: {}", format!("{:.2}ms", time).bright_green()); + } + + if let Some(count) = trace.activation_count { + println!(" 📊 Activation count: {}", count.to_string().bright_green()); + } + + println!(); +} + +pub fn print_agents_table(agents: &[Agent]) { + print_section_header(&format!("Agents ({})", agents.len())); + + if agents.is_empty() { + println!("{}", " No agents found.".bright_black()); + return; + } + + // Calculate column width + let max_id_len = agents.iter().map(|a| a.agent_id.len()).max().unwrap_or(8); + let id_width = max_id_len.max(8); + + // Print header + println!(" ┌{}┐", + "─".repeat(id_width + 2)); + + println!(" │ {: ProgressBar { + let pb = ProgressBar::new_spinner(); + pb.set_style( + ProgressStyle::default_spinner() + .template("{spinner:.cyan} {msg}") + .unwrap() + .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]), + ); + pb.set_message(message.to_string()); + pb.enable_steady_tick(std::time::Duration::from_millis(80)); + pb +} + +pub fn create_progress_bar(total: u64, message: &str) -> ProgressBar { + let pb = ProgressBar::new(total); + pb.set_style( + ProgressStyle::default_bar() + .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({percent}%)") + .unwrap() + .progress_chars("█▓▒░ "), + ); + pb.set_message(message.to_string()); + pb +} + +pub fn print_stored_memory(doc_id: &str, content: &str, is_async: bool) { + if is_async { + println!("{} Queued for background processing", "⏳".bright_yellow()); + } else { + println!("{} Stored successfully", "✓".bright_green()); + } + println!(" Document ID: {}", doc_id.bright_cyan()); + let preview = if content.len() > 60 { + format!("{}...", &content[..57]) + } else { + content.to_string() + }; + println!(" Content: {}", preview.bright_black()); + println!(); +} + +pub fn prompt_confirmation(message: &str) -> io::Result { + print!("{} {} [y/N]: ", "?".bright_blue().bold(), message); + io::stdout().flush()?; + + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + + Ok(input.trim().eq_ignore_ascii_case("y") || input.trim().eq_ignore_ascii_case("yes")) +} diff --git a/control-plane/.dockerignore b/memora-control-plane/.dockerignore similarity index 83% rename from control-plane/.dockerignore rename to memora-control-plane/.dockerignore index bedce897..7424eff5 100644 --- a/control-plane/.dockerignore +++ b/memora-control-plane/.dockerignore @@ -3,8 +3,7 @@ node_modules npm-debug.log .git .gitignore -.env.local -.env*.local +.env README.md Dockerfile .dockerignore diff --git a/memora-control-plane/.env.example b/memora-control-plane/.env.example new file mode 100644 index 00000000..c706ab84 --- /dev/null +++ b/memora-control-plane/.env.example @@ -0,0 +1,2 @@ +# Dataplane API URL +MEMORA_CP_DATAPLANE_API_URL=http://localhost:8080 diff --git a/control-plane/.eslintrc.json b/memora-control-plane/.eslintrc.json similarity index 100% rename from control-plane/.eslintrc.json rename to memora-control-plane/.eslintrc.json diff --git a/control-plane/.gitignore b/memora-control-plane/.gitignore similarity index 100% rename from control-plane/.gitignore rename to memora-control-plane/.gitignore diff --git a/memora-control-plane/Dockerfile b/memora-control-plane/Dockerfile new file mode 100644 index 00000000..55309122 --- /dev/null +++ b/memora-control-plane/Dockerfile @@ -0,0 +1,39 @@ +FROM node:20-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +RUN npm run build + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Automatically leverage output traces to reduce image size +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/control-plane/README.md b/memora-control-plane/README.md similarity index 96% rename from control-plane/README.md rename to memora-control-plane/README.md index 28805f94..44a01d6c 100644 --- a/control-plane/README.md +++ b/memora-control-plane/README.md @@ -75,16 +75,12 @@ npm install ### Configuration -Configure the dataplane URL in `.env.local`: +Configure the dataplane URL in `.env` (optional, defaults to http://localhost:8080): ```bash -cp .env.local.example .env.local -``` - -Edit `.env.local`: - -```env -DATAPLANE_API_URL=http://localhost:8080 +cat > .env << 'EOF' +MEMORA_CP_DATAPLANE_API_URL=http://localhost:8080 +EOF ``` ### Development @@ -157,7 +153,7 @@ control-plane/ │ ├── agent-context.tsx # Global agent state │ ├── api.ts # API client │ └── utils.ts # Utilities -├── .env.local # Environment config +├── .env # Environment config (optional) └── package.json ``` @@ -240,7 +236,7 @@ All control plane API routes proxy to the dataplane: **CORS Errors**: The control plane should eliminate CORS issues. If you see them, ensure you're accessing the control plane at `http://localhost:3000` (not the dataplane directly). -**Connection Errors**: Verify the dataplane is running at the URL specified in `.env.local` (default: `http://localhost:8080`). +**Connection Errors**: Verify the dataplane is running at the URL specified in `.env` (default: `http://localhost:8080`). **Graph Not Rendering**: Check browser console for errors. Ensure data is loading correctly from `/api/graph`. diff --git a/control-plane/build-docker.sh b/memora-control-plane/build-docker.sh similarity index 93% rename from control-plane/build-docker.sh rename to memora-control-plane/build-docker.sh index c8a6a13f..770c5c9a 100755 --- a/control-plane/build-docker.sh +++ b/memora-control-plane/build-docker.sh @@ -62,11 +62,11 @@ echo "Image: ${FULL_IMAGE_NAME}" echo "" echo "To run the container:" echo " docker run -p 3000:3000 \\" -echo " -e DATAPLANE_API_URL=http://your-api-url:8080 \\" +echo " -e MEMORA_CP_DATAPLANE_API_URL=http://your-api-url:8080 \\" echo " ${FULL_IMAGE_NAME}" echo "" echo "Or with an env file:" -echo " docker run -p 3000:3000 --env-file .env.local ${FULL_IMAGE_NAME}" +echo " docker run -p 3000:3000 --env-file .env ${FULL_IMAGE_NAME}" echo "" echo "To push to registry (if registry specified):" if [ -n "$REGISTRY" ]; then diff --git a/control-plane/components.json b/memora-control-plane/components.json similarity index 100% rename from control-plane/components.json rename to memora-control-plane/components.json diff --git a/control-plane/next.config.ts b/memora-control-plane/next.config.ts similarity index 100% rename from control-plane/next.config.ts rename to memora-control-plane/next.config.ts diff --git a/control-plane/package-lock.json b/memora-control-plane/package-lock.json similarity index 100% rename from control-plane/package-lock.json rename to memora-control-plane/package-lock.json diff --git a/control-plane/package.json b/memora-control-plane/package.json similarity index 100% rename from control-plane/package.json rename to memora-control-plane/package.json diff --git a/control-plane/postcss.config.mjs b/memora-control-plane/postcss.config.mjs similarity index 100% rename from control-plane/postcss.config.mjs rename to memora-control-plane/postcss.config.mjs diff --git a/control-plane/src/app/api/agents/route.ts b/memora-control-plane/src/app/api/agents/route.ts similarity index 82% rename from control-plane/src/app/api/agents/route.ts rename to memora-control-plane/src/app/api/agents/route.ts index 31272008..d0dce6fb 100644 --- a/control-plane/src/app/api/agents/route.ts +++ b/memora-control-plane/src/app/api/agents/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from 'next/server'; -const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080'; +const DATAPLANE_URL = process.env.MEMORA_CP_DATAPLANE_API_URL || 'http://localhost:8080'; export async function GET() { try { diff --git a/control-plane/src/app/api/documents/[documentId]/route.ts b/memora-control-plane/src/app/api/documents/[documentId]/route.ts similarity index 88% rename from control-plane/src/app/api/documents/[documentId]/route.ts rename to memora-control-plane/src/app/api/documents/[documentId]/route.ts index b0bd12be..f902c49f 100644 --- a/control-plane/src/app/api/documents/[documentId]/route.ts +++ b/memora-control-plane/src/app/api/documents/[documentId]/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080'; +const DATAPLANE_URL = process.env.MEMORA_CP_DATAPLANE_API_URL || 'http://localhost:8080'; export async function GET( request: NextRequest, diff --git a/control-plane/src/app/api/documents/route.ts b/memora-control-plane/src/app/api/documents/route.ts similarity index 86% rename from control-plane/src/app/api/documents/route.ts rename to memora-control-plane/src/app/api/documents/route.ts index 38e2e864..c315f632 100644 --- a/control-plane/src/app/api/documents/route.ts +++ b/memora-control-plane/src/app/api/documents/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080'; +const DATAPLANE_URL = process.env.MEMORA_CP_DATAPLANE_API_URL || 'http://localhost:8080'; export async function GET(request: NextRequest) { try { diff --git a/control-plane/src/app/api/graph/route.ts b/memora-control-plane/src/app/api/graph/route.ts similarity index 86% rename from control-plane/src/app/api/graph/route.ts rename to memora-control-plane/src/app/api/graph/route.ts index d81f6918..ac237f40 100644 --- a/control-plane/src/app/api/graph/route.ts +++ b/memora-control-plane/src/app/api/graph/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080'; +const DATAPLANE_URL = process.env.MEMORA_CP_DATAPLANE_API_URL || 'http://localhost:8080'; export async function GET(request: NextRequest) { try { diff --git a/control-plane/src/app/api/list/route.ts b/memora-control-plane/src/app/api/list/route.ts similarity index 86% rename from control-plane/src/app/api/list/route.ts rename to memora-control-plane/src/app/api/list/route.ts index 76c119ca..841c7bf0 100644 --- a/control-plane/src/app/api/list/route.ts +++ b/memora-control-plane/src/app/api/list/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080'; +const DATAPLANE_URL = process.env.MEMORA_CP_DATAPLANE_API_URL || 'http://localhost:8080'; export async function GET(request: NextRequest) { try { diff --git a/control-plane/src/app/api/memories/batch/route.ts b/memora-control-plane/src/app/api/memories/batch/route.ts similarity index 87% rename from control-plane/src/app/api/memories/batch/route.ts rename to memora-control-plane/src/app/api/memories/batch/route.ts index 1abe01d1..aa91ee2f 100644 --- a/control-plane/src/app/api/memories/batch/route.ts +++ b/memora-control-plane/src/app/api/memories/batch/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080'; +const DATAPLANE_URL = process.env.MEMORA_CP_DATAPLANE_API_URL || 'http://localhost:8080'; export async function POST(request: NextRequest) { try { diff --git a/control-plane/src/app/api/memories/batch_async/route.ts b/memora-control-plane/src/app/api/memories/batch_async/route.ts similarity index 88% rename from control-plane/src/app/api/memories/batch_async/route.ts rename to memora-control-plane/src/app/api/memories/batch_async/route.ts index 7a3b6ae6..ae2c1e0b 100644 --- a/control-plane/src/app/api/memories/batch_async/route.ts +++ b/memora-control-plane/src/app/api/memories/batch_async/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080'; +const DATAPLANE_URL = process.env.MEMORA_CP_DATAPLANE_API_URL || 'http://localhost:8080'; export async function POST(request: NextRequest) { try { diff --git a/control-plane/src/app/api/operations/[agentId]/route.ts b/memora-control-plane/src/app/api/operations/[agentId]/route.ts similarity index 86% rename from control-plane/src/app/api/operations/[agentId]/route.ts rename to memora-control-plane/src/app/api/operations/[agentId]/route.ts index 86782921..e03b86d7 100644 --- a/control-plane/src/app/api/operations/[agentId]/route.ts +++ b/memora-control-plane/src/app/api/operations/[agentId]/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080'; +const DATAPLANE_URL = process.env.MEMORA_CP_DATAPLANE_API_URL || 'http://localhost:8080'; export async function GET( request: NextRequest, diff --git a/control-plane/src/app/api/search/route.ts b/memora-control-plane/src/app/api/search/route.ts similarity index 87% rename from control-plane/src/app/api/search/route.ts rename to memora-control-plane/src/app/api/search/route.ts index 4fb844f9..7f5c0fb8 100644 --- a/control-plane/src/app/api/search/route.ts +++ b/memora-control-plane/src/app/api/search/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080'; +const DATAPLANE_URL = process.env.MEMORA_CP_DATAPLANE_API_URL || 'http://localhost:8080'; export async function POST(request: NextRequest) { try { diff --git a/control-plane/src/app/api/stats/[agentId]/route.ts b/memora-control-plane/src/app/api/stats/[agentId]/route.ts similarity index 86% rename from control-plane/src/app/api/stats/[agentId]/route.ts rename to memora-control-plane/src/app/api/stats/[agentId]/route.ts index bfb5aa74..29218c1d 100644 --- a/control-plane/src/app/api/stats/[agentId]/route.ts +++ b/memora-control-plane/src/app/api/stats/[agentId]/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080'; +const DATAPLANE_URL = process.env.MEMORA_CP_DATAPLANE_API_URL || 'http://localhost:8080'; export async function GET( request: NextRequest, diff --git a/control-plane/src/app/api/think/route.ts b/memora-control-plane/src/app/api/think/route.ts similarity index 87% rename from control-plane/src/app/api/think/route.ts rename to memora-control-plane/src/app/api/think/route.ts index 98b0bbb1..3c5e90c5 100644 --- a/control-plane/src/app/api/think/route.ts +++ b/memora-control-plane/src/app/api/think/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -const DATAPLANE_URL = process.env.DATAPLANE_API_URL || 'http://localhost:8080'; +const DATAPLANE_URL = process.env.MEMORA_CP_DATAPLANE_API_URL || 'http://localhost:8080'; export async function POST(request: NextRequest) { try { diff --git a/control-plane/src/app/dashboard/page.tsx b/memora-control-plane/src/app/dashboard/page.tsx similarity index 100% rename from control-plane/src/app/dashboard/page.tsx rename to memora-control-plane/src/app/dashboard/page.tsx diff --git a/control-plane/src/app/globals.css b/memora-control-plane/src/app/globals.css similarity index 100% rename from control-plane/src/app/globals.css rename to memora-control-plane/src/app/globals.css diff --git a/control-plane/src/app/layout.tsx b/memora-control-plane/src/app/layout.tsx similarity index 100% rename from control-plane/src/app/layout.tsx rename to memora-control-plane/src/app/layout.tsx diff --git a/control-plane/src/app/page.tsx b/memora-control-plane/src/app/page.tsx similarity index 100% rename from control-plane/src/app/page.tsx rename to memora-control-plane/src/app/page.tsx diff --git a/control-plane/src/components/add-memory-view.tsx b/memora-control-plane/src/components/add-memory-view.tsx similarity index 100% rename from control-plane/src/components/add-memory-view.tsx rename to memora-control-plane/src/components/add-memory-view.tsx diff --git a/control-plane/src/components/agent-selector.tsx b/memora-control-plane/src/components/agent-selector.tsx similarity index 100% rename from control-plane/src/components/agent-selector.tsx rename to memora-control-plane/src/components/agent-selector.tsx diff --git a/control-plane/src/components/data-view.tsx b/memora-control-plane/src/components/data-view.tsx similarity index 100% rename from control-plane/src/components/data-view.tsx rename to memora-control-plane/src/components/data-view.tsx diff --git a/control-plane/src/components/documents-view.tsx b/memora-control-plane/src/components/documents-view.tsx similarity index 100% rename from control-plane/src/components/documents-view.tsx rename to memora-control-plane/src/components/documents-view.tsx diff --git a/control-plane/src/components/search-debug-view.tsx b/memora-control-plane/src/components/search-debug-view.tsx similarity index 100% rename from control-plane/src/components/search-debug-view.tsx rename to memora-control-plane/src/components/search-debug-view.tsx diff --git a/control-plane/src/components/stats-view.tsx b/memora-control-plane/src/components/stats-view.tsx similarity index 100% rename from control-plane/src/components/stats-view.tsx rename to memora-control-plane/src/components/stats-view.tsx diff --git a/control-plane/src/components/think-view.tsx b/memora-control-plane/src/components/think-view.tsx similarity index 100% rename from control-plane/src/components/think-view.tsx rename to memora-control-plane/src/components/think-view.tsx diff --git a/control-plane/src/lib/agent-context.tsx b/memora-control-plane/src/lib/agent-context.tsx similarity index 100% rename from control-plane/src/lib/agent-context.tsx rename to memora-control-plane/src/lib/agent-context.tsx diff --git a/control-plane/src/lib/api.ts b/memora-control-plane/src/lib/api.ts similarity index 98% rename from control-plane/src/lib/api.ts rename to memora-control-plane/src/lib/api.ts index 862e5682..58c3c912 100644 --- a/control-plane/src/lib/api.ts +++ b/memora-control-plane/src/lib/api.ts @@ -213,7 +213,7 @@ export class ServerDataplaneClient { private baseUrl: string; constructor() { - this.baseUrl = process.env.DATAPLANE_API_URL || 'http://localhost:8080'; + this.baseUrl = process.env.MEMORA_CP_DATAPLANE_API_URL || 'http://localhost:8080'; } async fetchDataplane( diff --git a/control-plane/src/lib/utils.ts b/memora-control-plane/src/lib/utils.ts similarity index 100% rename from control-plane/src/lib/utils.ts rename to memora-control-plane/src/lib/utils.ts diff --git a/memora-control-plane/start-server.sh b/memora-control-plane/start-server.sh new file mode 100755 index 00000000..f1c4560e --- /dev/null +++ b/memora-control-plane/start-server.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Wrapper script to map MEMORA_CP_* environment variables to Next.js standard variables + +# Map prefixed env vars to standard Next.js env vars +export HOSTNAME="${MEMORA_CP_HOSTNAME:-0.0.0.0}" +export PORT="${MEMORA_CP_PORT:-3000}" + +# Start the Next.js server +# The server.js is in the standalone output at the root +exec node server.js diff --git a/control-plane/tailwind.config.ts b/memora-control-plane/tailwind.config.ts similarity index 100% rename from control-plane/tailwind.config.ts rename to memora-control-plane/tailwind.config.ts diff --git a/control-plane/tsconfig.json b/memora-control-plane/tsconfig.json similarity index 100% rename from control-plane/tsconfig.json rename to memora-control-plane/tsconfig.json diff --git a/benchmarks/.DS_Store b/memora-dev/benchmarks/.DS_Store similarity index 98% rename from benchmarks/.DS_Store rename to memora-dev/benchmarks/.DS_Store index b4d6cfce..15b5bd44 100644 Binary files a/benchmarks/.DS_Store and b/memora-dev/benchmarks/.DS_Store differ diff --git a/benchmarks/__init__.py b/memora-dev/benchmarks/__init__.py similarity index 100% rename from benchmarks/__init__.py rename to memora-dev/benchmarks/__init__.py diff --git a/benchmarks/benchmarks-results/locomo/73.5.json b/memora-dev/benchmarks/benchmarks-results/locomo/73.5.json similarity index 100% rename from benchmarks/benchmarks-results/locomo/73.5.json rename to memora-dev/benchmarks/benchmarks-results/locomo/73.5.json diff --git a/benchmarks/benchmarks-results/longmemeval/final.json b/memora-dev/benchmarks/benchmarks-results/longmemeval/final.json similarity index 100% rename from benchmarks/benchmarks-results/longmemeval/final.json rename to memora-dev/benchmarks/benchmarks-results/longmemeval/final.json diff --git a/benchmarks/common/__init__.py b/memora-dev/benchmarks/common/__init__.py similarity index 100% rename from benchmarks/common/__init__.py rename to memora-dev/benchmarks/common/__init__.py diff --git a/benchmarks/common/benchmark_runner.py b/memora-dev/benchmarks/common/benchmark_runner.py similarity index 99% rename from benchmarks/common/benchmark_runner.py rename to memora-dev/benchmarks/common/benchmark_runner.py index 01f58767..a67b95e1 100644 --- a/benchmarks/common/benchmark_runner.py +++ b/memora-dev/benchmarks/common/benchmark_runner.py @@ -230,11 +230,11 @@ class BenchmarkRunner: self.answer_generator = answer_generator self.answer_evaluator = answer_evaluator self.memory = memory or TemporalSemanticMemory( - db_url=os.getenv("DATABASE_URL"), - memory_llm_provider=os.getenv("MEMORY_LLM_PROVIDER", "groq"), - memory_llm_api_key=os.getenv("MEMORY_LLM_API_KEY"), - memory_llm_model=os.getenv("MEMORY_LLM_MODEL", "openai/gpt-oss-120b"), - memory_llm_base_url=os.getenv("MEMORY_LLM_BASE_URL") or None, # Use None to get provider defaults + db_url=os.getenv("MEMORA_API_DATABASE_URL"), + memory_llm_provider=os.getenv("MEMORA_API_LLM_PROVIDER", "groq"), + memory_llm_api_key=os.getenv("MEMORA_API_LLM_API_KEY"), + memory_llm_model=os.getenv("MEMORA_API_LLM_MODEL", "openai/gpt-oss-120b"), + memory_llm_base_url=os.getenv("MEMORA_API_LLM_BASE_URL") or None, # Use None to get provider defaults ) def calculate_data_stats(self, items: List[Dict[str, Any]]) -> Dict[str, Any]: diff --git a/benchmarks/locomo/__init__.py b/memora-dev/benchmarks/locomo/__init__.py similarity index 100% rename from benchmarks/locomo/__init__.py rename to memora-dev/benchmarks/locomo/__init__.py diff --git a/benchmarks/locomo/datasets/locomo10.json b/memora-dev/benchmarks/locomo/datasets/locomo10.json similarity index 100% rename from benchmarks/locomo/datasets/locomo10.json rename to memora-dev/benchmarks/locomo/datasets/locomo10.json diff --git a/benchmarks/locomo/locomo_benchmark.py b/memora-dev/benchmarks/locomo/locomo_benchmark.py similarity index 97% rename from benchmarks/locomo/locomo_benchmark.py rename to memora-dev/benchmarks/locomo/locomo_benchmark.py index bb7f0141..2a129257 100644 --- a/benchmarks/locomo/locomo_benchmark.py +++ b/memora-dev/benchmarks/locomo/locomo_benchmark.py @@ -350,11 +350,11 @@ async def run_benchmark( memory = RemoteMemoryClient(base_url=api_url) else: memory = TemporalSemanticMemory( - db_url=os.getenv("DATABASE_URL"), - memory_llm_provider=os.getenv("MEMORY_LLM_PROVIDER", "groq"), - memory_llm_api_key=os.getenv("MEMORY_LLM_API_KEY"), - memory_llm_model=os.getenv("MEMORY_LLM_MODEL", "openai/gpt-oss-120b"), - memory_llm_base_url=os.getenv("MEMORY_LLM_BASE_URL") or None, # Use None to get provider defaults + db_url=os.getenv("MEMORA_API_DATABASE_URL"), + memory_llm_provider=os.getenv("MEMORA_API_LLM_PROVIDER", "groq"), + memory_llm_api_key=os.getenv("MEMORA_API_LLM_API_KEY"), + memory_llm_model=os.getenv("MEMORA_API_LLM_MODEL", "openai/gpt-oss-120b"), + memory_llm_base_url=os.getenv("MEMORA_API_LLM_BASE_URL") or None, # Use None to get provider defaults ) await memory.initialize() diff --git a/benchmarks/locomo/results/benchmark_results.json b/memora-dev/benchmarks/locomo/results/benchmark_results.json similarity index 100% rename from benchmarks/locomo/results/benchmark_results.json rename to memora-dev/benchmarks/locomo/results/benchmark_results.json diff --git a/benchmarks/locomo/results/benchmark_results_think.json b/memora-dev/benchmarks/locomo/results/benchmark_results_think.json similarity index 100% rename from benchmarks/locomo/results/benchmark_results_think.json rename to memora-dev/benchmarks/locomo/results/benchmark_results_think.json diff --git a/benchmarks/locomo/results/results_table.md b/memora-dev/benchmarks/locomo/results/results_table.md similarity index 100% rename from benchmarks/locomo/results/results_table.md rename to memora-dev/benchmarks/locomo/results/results_table.md diff --git a/benchmarks/locomo/results/results_table_think.md b/memora-dev/benchmarks/locomo/results/results_table_think.md similarity index 100% rename from benchmarks/locomo/results/results_table_think.md rename to memora-dev/benchmarks/locomo/results/results_table_think.md diff --git a/benchmarks/longmemeval/__init__.py b/memora-dev/benchmarks/longmemeval/__init__.py similarity index 100% rename from benchmarks/longmemeval/__init__.py rename to memora-dev/benchmarks/longmemeval/__init__.py diff --git a/benchmarks/longmemeval/longmemeval_benchmark.py b/memora-dev/benchmarks/longmemeval/longmemeval_benchmark.py similarity index 98% rename from benchmarks/longmemeval/longmemeval_benchmark.py rename to memora-dev/benchmarks/longmemeval/longmemeval_benchmark.py index 3ef7fbf9..f5c7088b 100644 --- a/benchmarks/longmemeval/longmemeval_benchmark.py +++ b/memora-dev/benchmarks/longmemeval/longmemeval_benchmark.py @@ -331,11 +331,11 @@ async def run_benchmark( memory = RemoteMemoryClient(base_url=api_url) else: memory = TemporalSemanticMemory( - db_url=os.getenv("DATABASE_URL"), - memory_llm_provider=os.getenv("MEMORY_LLM_PROVIDER", "groq"), - memory_llm_api_key=os.getenv("MEMORY_LLM_API_KEY"), - memory_llm_model=os.getenv("MEMORY_LLM_MODEL", "openai/gpt-oss-120b"), - memory_llm_base_url=os.getenv("MEMORY_LLM_BASE_URL") or None, # Use None to get provider defaults + db_url=os.getenv("MEMORA_API_DATABASE_URL"), + memory_llm_provider=os.getenv("MEMORA_API_LLM_PROVIDER", "groq"), + memory_llm_api_key=os.getenv("MEMORA_API_LLM_API_KEY"), + memory_llm_model=os.getenv("MEMORA_API_LLM_MODEL", "openai/gpt-oss-120b"), + memory_llm_base_url=os.getenv("MEMORA_API_LLM_BASE_URL") or None, # Use None to get provider defaults ) # Create benchmark runner diff --git a/benchmarks/longmemeval/results/benchmark_results.json b/memora-dev/benchmarks/longmemeval/results/benchmark_results.json similarity index 100% rename from benchmarks/longmemeval/results/benchmark_results.json rename to memora-dev/benchmarks/longmemeval/results/benchmark_results.json diff --git a/benchmarks/pyproject.toml b/memora-dev/benchmarks/pyproject.toml similarity index 92% rename from benchmarks/pyproject.toml rename to memora-dev/benchmarks/pyproject.toml index 732b6c8d..37e489c9 100644 --- a/benchmarks/pyproject.toml +++ b/memora-dev/benchmarks/pyproject.toml @@ -20,4 +20,4 @@ dependencies = [ packages = ["benchmarks"] [tool.uv.sources] -memora = { workspace = true } +memora = { path = "../../memora" } diff --git a/benchmarks/visualizer/__init__.py b/memora-dev/benchmarks/visualizer/__init__.py similarity index 100% rename from benchmarks/visualizer/__init__.py rename to memora-dev/benchmarks/visualizer/__init__.py diff --git a/benchmarks/visualizer/main.py b/memora-dev/benchmarks/visualizer/main.py similarity index 100% rename from benchmarks/visualizer/main.py rename to memora-dev/benchmarks/visualizer/main.py diff --git a/memora-dev/memora_dev/generate_openapi.py b/memora-dev/memora_dev/generate_openapi.py index fb2b88ee..5c95e638 100644 --- a/memora-dev/memora_dev/generate_openapi.py +++ b/memora-dev/memora_dev/generate_openapi.py @@ -12,8 +12,14 @@ from pathlib import Path from memora.api import create_app from memora import TemporalSemanticMemory -def generate_openapi_spec(output_path: str = "openapi.json"): +def generate_openapi_spec(output_path: str = None): """Generate OpenAPI spec and save to file.""" + # Default to root openapi.json if no path specified + if output_path is None: + # Get the root of the project (3 levels up from this file) + root_dir = Path(__file__).parent.parent.parent + output_path = str(root_dir / "openapi.json") + # Create a temporary memory instance for OpenAPI generation _memory = TemporalSemanticMemory( db_url="mock", diff --git a/memora-dev/pyproject.toml b/memora-dev/pyproject.toml index e6c5e84d..a6fe3c00 100644 --- a/memora-dev/pyproject.toml +++ b/memora-dev/pyproject.toml @@ -16,3 +16,6 @@ packages = ["memora_dev"] [tool.uv.sources] memora = { workspace = true } + +[project.scripts] +generate-openapi = "memora_dev.generate_openapi:generate_openapi_spec" diff --git a/memora/alembic/env.py b/memora/alembic/env.py index 167bd283..8c71ffcc 100644 --- a/memora/alembic/env.py +++ b/memora/alembic/env.py @@ -2,10 +2,10 @@ Alembic environment configuration for SQLAlchemy with pgvector. Uses synchronous psycopg2 driver for migrations to avoid pgbouncer issues. """ +import logging import os import sys from pathlib import Path -from logging.config import fileConfig from sqlalchemy import pool, engine_from_config from sqlalchemy.engine import Connection @@ -16,25 +16,19 @@ from dotenv import load_dotenv # Import your models here from memora.models import Base -# Load environment variables based on DATABASE_URL env var or default to local +# Load environment variables based on MEMORA_API_DATABASE_URL env var or default to local def load_env(): - """Load environment variables from .env.local or .env.dev""" - # Check if DATABASE_URL is already set (e.g., by CI/CD) - if os.getenv("DATABASE_URL"): + """Load environment variables from .env""" + # Check if MEMORA_API_DATABASE_URL is already set (e.g., by CI/CD) + if os.getenv("MEMORA_API_DATABASE_URL"): return - # Look for .env files in the parent directory (root of the workspace) + # Look for .env file in the parent directory (root of the workspace) root_dir = Path(__file__).parent.parent.parent + env_file = root_dir / ".env" - # Default to local environment - env_file = root_dir / ".env.local" if env_file.exists(): load_dotenv(env_file) - else: - # Fallback to dev - env_file = root_dir / ".env.dev" - if env_file.exists(): - load_dotenv(env_file) load_env() @@ -42,25 +36,8 @@ load_env() # access to the values within the .ini file in use. config = context.config -# Interpret the config file for Python logging. -# This line sets up loggers basically. -if config.config_file_name is not None: - fileConfig(config.config_file_name) - -# Get database URL from environment -database_url = os.getenv("DATABASE_URL") -if not database_url: - raise ValueError("DATABASE_URL environment variable is not set") - -# For migrations, use psycopg2 (sync driver) to avoid pgbouncer prepared statement issues -# The application uses asyncpg, but migrations work better with psycopg2 -if database_url.startswith("postgresql+asyncpg://"): - database_url = database_url.replace("postgresql+asyncpg://", "postgresql://", 1) -elif database_url.startswith("postgres+asyncpg://"): - database_url = database_url.replace("postgres+asyncpg://", "postgresql://", 1) - -# Override the sqlalchemy.url in alembic.ini -config.set_main_option("sqlalchemy.url", database_url) +# Note: We don't call fileConfig() here to avoid overriding the application's logging configuration. +# Alembic will use the existing logging configuration from the application. # add your model's MetaData object here # for 'autogenerate' support @@ -72,6 +49,34 @@ target_metadata = Base.metadata # ... etc. +def get_database_url() -> str: + """ + Get and process the database URL from config or environment. + + Returns the URL with the correct driver (psycopg2) for migrations. + """ + # Get database URL from config (set programmatically) or environment + database_url = config.get_main_option("sqlalchemy.url") + if not database_url: + database_url = os.getenv("MEMORA_API_DATABASE_URL") + if not database_url: + raise ValueError( + "Database URL not found. " + "Set MEMORA_API_DATABASE_URL environment variable or pass database_url to run_migrations()." + ) + + # For migrations, use psycopg2 (sync driver) to avoid pgbouncer prepared statement issues + if database_url.startswith("postgresql+asyncpg://"): + database_url = database_url.replace("postgresql+asyncpg://", "postgresql://", 1) + elif database_url.startswith("postgres+asyncpg://"): + database_url = database_url.replace("postgres+asyncpg://", "postgresql://", 1) + + # Update config with processed URL for engine_from_config to use + config.set_main_option("sqlalchemy.url", database_url) + + return database_url + + def run_migrations_offline() -> None: """Run migrations in 'offline' mode. @@ -84,9 +89,11 @@ def run_migrations_offline() -> None: script output. """ - url = config.get_main_option("sqlalchemy.url") + logging.info("running offline") + database_url = get_database_url() + context.configure( - url=url, + url=database_url, target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"}, @@ -98,6 +105,8 @@ def run_migrations_offline() -> None: def run_migrations_online() -> None: """Run migrations in 'online' mode with synchronous engine.""" + get_database_url() # Process and set the database URL in config + connectable = engine_from_config( config.get_section(config.config_ini_section, {}), prefix="sqlalchemy.", diff --git a/memora/memora/api.py b/memora/memora/api.py index 48a9908f..8a5b5e73 100644 --- a/memora/memora/api.py +++ b/memora/memora/api.py @@ -13,7 +13,7 @@ from datetime import datetime from fastapi import FastAPI, HTTPException from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse -from pydantic import BaseModel +from pydantic import BaseModel, Field from memora import TemporalSemanticMemory @@ -44,9 +44,32 @@ class SearchRequest(BaseModel): } +class SearchResult(BaseModel): + """Single search result item.""" + model_config = { + "populate_by_name": True, + "json_schema_extra": { + "example": { + "id": "123e4567-e89b-12d3-a456-426614174000", + "text": "Alice works at Google on the AI team", + "type": "world", + "context": "work info", + "event_date": "2024-01-15T10:30:00Z" + } + } + } + + id: str + text: str + type: Optional[str] = None # fact type: world, agent, opinion + activation: Optional[float] = None + context: Optional[str] = None + event_date: Optional[str] = None # ISO format date string + + class SearchResponse(BaseModel): """Response model for search endpoints.""" - results: List[Dict[str, Any]] + results: List[SearchResult] trace: Optional[Dict[str, Any]] = None class Config: @@ -54,9 +77,12 @@ class SearchResponse(BaseModel): "example": { "results": [ { + "id": "123e4567-e89b-12d3-a456-426614174000", "text": "Alice works at Google on the AI team", - "score": 0.95, - "id": "123e4567-e89b-12d3-a456-426614174000" + "type": "world", + "activation": 0.95, + "context": "work info", + "event_date": "2024-01-15T10:30:00Z" } ], "trace": { @@ -173,23 +199,53 @@ class OpinionItem(BaseModel): confidence: float +class ThinkFact(BaseModel): + """A fact used in think response.""" + id: Optional[str] = None + text: str + type: Optional[str] = None # fact type: world, agent, opinion + activation: Optional[float] = None + context: Optional[str] = None + event_date: Optional[str] = None + + class Config: + json_schema_extra = { + "example": { + "id": "123e4567-e89b-12d3-a456-426614174000", + "text": "AI is used in healthcare", + "type": "world", + "context": "healthcare discussion", + "event_date": "2024-01-15T10:30:00Z" + } + } + + class ThinkResponse(BaseModel): """Response model for think endpoint.""" text: str - based_on: Dict[str, List[Dict[str, Any]]] # {"world": [...], "agent": [...], "opinion": [...]} - new_opinions: List[OpinionItem] = [] # List of newly formed opinions with confidence + based_on: List[ThinkFact] = [] # Facts used to generate the response + new_opinions: List[str] = [] # Simplified to list of opinion strings class Config: json_schema_extra = { "example": { "text": "Based on my understanding, AI is a transformative technology...", - "based_on": { - "world": [{"text": "AI is used in healthcare", "score": 0.9}], - "agent": [{"text": "I discussed AI applications last week", "score": 0.85}], - "opinion": [{"text": "I believe AI should be used ethically", "score": 0.8}] - }, + "based_on": [ + { + "id": "123", + "text": "AI is used in healthcare", + "type": "world", + "activation": 0.9 + }, + { + "id": "456", + "text": "I discussed AI applications last week", + "type": "agent", + "activation": 0.85 + } + ], "new_opinions": [ - {"text": "AI has great potential when used responsibly", "confidence": 0.95} + "AI has great potential when used responsibly" ] } } @@ -355,7 +411,13 @@ The system uses: @app.on_event("startup") async def startup_event(): - """Initialize memory system on startup.""" + """Initialize database and memory system on startup.""" + from memora.migrations import run_migrations + + # Run database migrations first + run_migrations(memory.db_url) + + # Then initialize memory system await memory.initialize() logging.info("Memory system initialized") @@ -483,7 +545,7 @@ def _register_routes(app: FastAPI): ) # Run search with tracing - results, trace = await app.state.memory.search_async( + core_result = await app.state.memory.search_async( agent_id=request.agent_id, query=request.query, thinking_budget=request.thinking_budget, @@ -494,23 +556,21 @@ def _register_routes(app: FastAPI): question_date=question_date ) - # Filter results to only include specific fields - filtered_results = [ - { - "id": result.get("id"), - "text": result.get("text"), - "context": result.get("context"), - "event_date": result.get("event_date") - } - for result in results + # Convert core MemoryFact objects to API SearchResult objects (excluding internal metrics) + search_results = [ + SearchResult( + id=fact.id, + text=fact.text, + type=fact.fact_type, + context=fact.context, + event_date=fact.event_date + ) + for fact in core_result.results ] - # Convert trace to dict - trace_dict = trace.to_dict() if trace else None - return SearchResponse( - results=filtered_results, - trace=trace_dict + results=search_results, + trace=core_result.trace ) except HTTPException: raise @@ -541,16 +601,28 @@ def _register_routes(app: FastAPI): async def api_think(request: ThinkRequest): try: # Use the memory system's think_async method - result = await app.state.memory.think_async( + core_result = await app.state.memory.think_async( agent_id=request.agent_id, query=request.query, thinking_budget=request.thinking_budget ) + # Convert core MemoryFact objects to API ThinkFact objects (excluding internal metrics) + based_on_facts = [] + for fact_type, facts in core_result.based_on.items(): + for fact in facts: + based_on_facts.append(ThinkFact( + id=fact.id, + text=fact.text, + type=fact.fact_type, + context=fact.context, + event_date=fact.event_date + )) + return ThinkResponse( - text=result["text"], - based_on=result["based_on"], - new_opinions=result.get("new_opinions", []) + text=core_result.text, + based_on=based_on_facts, + new_opinions=core_result.new_opinions ) except Exception as e: diff --git a/memora/memora/llm_wrapper.py b/memora/memora/llm_wrapper.py index 92afe236..8548f74c 100644 --- a/memora/memora/llm_wrapper.py +++ b/memora/memora/llm_wrapper.py @@ -182,10 +182,10 @@ class LLMConfig: @classmethod def for_memory(cls) -> "LLMConfig": """Create configuration for memory operations from environment variables.""" - provider = os.getenv("MEMORY_LLM_PROVIDER", "groq") - api_key = os.getenv("MEMORY_LLM_API_KEY") - base_url = os.getenv("MEMORY_LLM_BASE_URL") - model = os.getenv("MEMORY_LLM_MODEL", "openai/gpt-oss-120b") + provider = os.getenv("MEMORA_API_LLM_PROVIDER", "groq") + api_key = os.getenv("MEMORA_API_LLM_API_KEY") + base_url = os.getenv("MEMORA_API_LLM_BASE_URL") + model = os.getenv("MEMORA_API_LLM_MODEL", "openai/gpt-oss-120b") # Set default base URL if not provided if not base_url: @@ -211,10 +211,10 @@ class LLMConfig: Falls back to memory LLM config if judge-specific config not set. """ # Check if judge-specific config exists, otherwise fall back to memory config - provider = os.getenv("JUDGE_LLM_PROVIDER", os.getenv("MEMORY_LLM_PROVIDER", "groq")) - api_key = os.getenv("JUDGE_LLM_API_KEY", os.getenv("MEMORY_LLM_API_KEY")) - base_url = os.getenv("JUDGE_LLM_BASE_URL", os.getenv("MEMORY_LLM_BASE_URL")) - model = os.getenv("JUDGE_LLM_MODEL", os.getenv("MEMORY_LLM_MODEL", "openai/gpt-oss-120b")) + provider = os.getenv("MEMORA_API_JUDGE_LLM_PROVIDER", os.getenv("MEMORA_API_LLM_PROVIDER", "groq")) + api_key = os.getenv("MEMORA_API_JUDGE_LLM_API_KEY", os.getenv("MEMORA_API_LLM_API_KEY")) + base_url = os.getenv("MEMORA_API_JUDGE_LLM_BASE_URL", os.getenv("MEMORA_API_LLM_BASE_URL")) + model = os.getenv("MEMORA_API_JUDGE_LLM_MODEL", os.getenv("MEMORA_API_LLM_MODEL", "openai/gpt-oss-120b")) # Set default base URL if not provided if not base_url: diff --git a/memora/memora/migrations.py b/memora/memora/migrations.py new file mode 100644 index 00000000..5e1cc222 --- /dev/null +++ b/memora/memora/migrations.py @@ -0,0 +1,114 @@ +""" +Database migration management using Alembic. + +This module provides programmatic access to run database migrations +on application startup. It is designed to be safe for concurrent +execution - Alembic uses PostgreSQL transactions to prevent +conflicts when multiple instances start simultaneously. + +Important: All migrations must be backward-compatible to allow +safe rolling deployments. +""" +import logging +import os +from pathlib import Path + +from alembic import command +from alembic.config import Config + +logger = logging.getLogger(__name__) + + +def run_migrations(database_url: str) -> None: + """ + Run database migrations to the latest version. + + This function is safe to call on every application startup: + - Alembic checks the current schema version in the database + - Only missing migrations are applied + - PostgreSQL transactions prevent concurrent migration conflicts + - If schema is already up-to-date, this is a fast no-op + + Raises: + RuntimeError: If migrations fail to complete + FileNotFoundError: If alembic.ini is not found + """ + try: + # Find alembic.ini - it should be at the project root + # This file is in: memora/memora/migrations.py + # Project root is: memora/ + project_root = Path(__file__).parent.parent + alembic_ini = project_root / "alembic.ini" + + if not alembic_ini.exists(): + raise FileNotFoundError( + f"alembic.ini not found at {alembic_ini}. " + "Database migrations cannot be run." + ) + + logger.info(f"Running database migrations to head...") + logger.info(f"Database URL: {database_url}") + + # Create Alembic configuration from ini file + alembic_cfg = Config(str(alembic_ini)) + alembic_cfg.set_main_option("sqlalchemy.url", database_url) + + # Run migrations to head (latest version) + # Note: Alembic may call sys.exit() on errors instead of raising exceptions + # We rely on the outer try/except and logging to catch issues + command.upgrade(alembic_cfg, "head") + + logger.info("Database migrations completed successfully") + + except FileNotFoundError: + logger.error("alembic.ini not found, database migrations cannot be run") + raise + except SystemExit as e: + # Catch sys.exit() calls from Alembic + logger.error(f"Alembic called sys.exit() with code: {e.code}", exc_info=True) + raise RuntimeError(f"Database migration failed with exit code {e.code}") from e + except Exception as e: + logger.error(f"Failed to run database migrations: {e}", exc_info=True) + raise RuntimeError("Database migration failed") from e + + +def check_migration_status() -> tuple[str | None, str | None]: + """ + Check current database schema version and latest available version. + + Returns: + Tuple of (current_revision, head_revision) + Returns (None, None) if unable to determine versions + """ + try: + from alembic.runtime.migration import MigrationContext + from alembic.script import ScriptDirectory + from sqlalchemy import create_engine + + database_url = os.getenv("MEMORA_API_DATABASE_URL") + if not database_url: + logger.warning("MEMORA_API_DATABASE_URL not set, cannot check migration status") + return None, None + + # Get current revision from database + engine = create_engine(database_url) + with engine.connect() as connection: + context = MigrationContext.configure(connection) + current_rev = context.get_current_revision() + + # Get head revision from migration scripts + project_root = Path(__file__).parent.parent + alembic_ini = project_root / "alembic.ini" + + if not alembic_ini.exists(): + return current_rev, None + + alembic_cfg = Config(str(alembic_ini)) + script = ScriptDirectory.from_config(alembic_cfg) + head_rev = script.get_current_head() + + return current_rev, head_rev + + except Exception as e: + logger.warning(f"Unable to check migration status: {e}") + return None, None diff --git a/memora/memora/operations/think_operations.py b/memora/memora/operations/think_operations.py index 37fc3c77..a6f5df43 100644 --- a/memora/memora/operations/think_operations.py +++ b/memora/memora/operations/think_operations.py @@ -8,6 +8,8 @@ from datetime import datetime, timezone from typing import Dict, List, Any from pydantic import BaseModel, Field +from ..response_models import ThinkResult, MemoryFact + logger = logging.getLogger(__name__) @@ -19,7 +21,7 @@ class ThinkOperationsMixin: agent_id: str, query: str, thinking_budget: int = 50, - ) -> Dict[str, Any]: + ) -> ThinkResult: """ Think and formulate an answer using agent identity, world facts, and opinions. @@ -37,18 +39,18 @@ class ThinkOperationsMixin: thinking_budget: Number of memory units to explore Returns: - Dict with: + ThinkResult containing: - text: Plain text answer (no markdown) - - based_on: Dict with 'world', 'agent', and 'opinion' fact lists + - based_on: Dict with 'world', 'agent', and 'opinion' fact lists (MemoryFact objects) - new_opinions: List of newly formed opinions """ # Use cached LLM config if self._llm_config is None: - raise ValueError("Memory LLM API key not set. Set MEMORY_LLM_API_KEY environment variable.") + raise ValueError("Memory LLM API key not set. Set MEMORA_API_LLM_API_KEY environment variable.") # Steps 1-3: Run multi-fact-type search (12-way retrieval: 4 methods × 3 fact types) # This is more efficient than 3 separate searches as it merges and reranks all results together - all_results, _ = await self.search_async( + search_result = await self.search_async( agent_id=agent_id, query=query, thinking_budget=thinking_budget, @@ -57,12 +59,13 @@ class ThinkOperationsMixin: fact_type=['agent', 'world', 'opinion'] ) + all_results = search_result.results logger.info(f"[THINK] Search returned {len(all_results)} results") # Split results by fact type for structured response - agent_results = [r for r in all_results if r.get('fact_type') == 'agent'] - world_results = [r for r in all_results if r.get('fact_type') == 'world'] - opinion_results = [r for r in all_results if r.get('fact_type') == 'opinion'] + agent_results = [r for r in all_results if r.fact_type == 'agent'] + world_results = [r for r in all_results if r.fact_type == 'world'] + opinion_results = [r for r in all_results if r.fact_type == 'opinion'] logger.info(f"[THINK] Split results - agent: {len(agent_results)}, world: {len(world_results)}, opinion: {len(opinion_results)}") @@ -75,25 +78,25 @@ class ThinkOperationsMixin: formatted = [] for fact in facts: fact_obj = { - "text": fact['text'] + "text": fact.text } # Add context if available - if fact.get('context'): - fact_obj["context"] = fact['context'] + if fact.context: + fact_obj["context"] = fact.context # Add event_date if available - if fact.get('event_date'): + if fact.event_date: from datetime import datetime - event_date = fact['event_date'] + event_date = fact.event_date if isinstance(event_date, str): fact_obj["event_date"] = event_date elif isinstance(event_date, datetime): fact_obj["event_date"] = event_date.strftime('%Y-%m-%d %H:%M:%S') - # Add score if available - if fact.get('score') is not None: - fact_obj["score"] = fact['score'] + # Add activation if available + if fact.activation is not None: + fact_obj["score"] = fact.activation formatted.append(fact_obj) @@ -155,15 +158,15 @@ If you form any new opinions while thinking about this question, state them clea logger.debug(f"[THINK] form_opinion task submitted") # Step 7: Return response with facts split by type (don't wait for opinions) - return { - "text": answer_text, - "based_on": { + return ThinkResult( + text=answer_text, + based_on={ "world": world_results, "agent": agent_results, "opinion": opinion_results }, - "new_opinions": [] # Opinions are being extracted asynchronously - } + new_opinions=[] # Opinions are being extracted asynchronously + ) async def _extract_and_store_opinions_async( self, diff --git a/memora/memora/response_models.py b/memora/memora/response_models.py new file mode 100644 index 00000000..dfbc4879 --- /dev/null +++ b/memora/memora/response_models.py @@ -0,0 +1,129 @@ +""" +Core response models for Memora memory system. + +These models define the structure of data returned by the core TemporalSemanticMemory class. +API response models should be kept separate and convert from these core models to maintain +API stability even if internal models change. +""" + +from typing import Optional, List, Dict, Any +from pydantic import BaseModel, Field + + +class MemoryFact(BaseModel): + """ + A single memory fact returned by search or think operations. + + This represents a unit of information stored in the memory system, + including both the content and metadata. + """ + id: str = Field(description="Unique identifier for the memory fact") + text: str = Field(description="The actual text content of the memory") + fact_type: str = Field(description="Type of fact: 'world', 'agent', or 'opinion'") + context: Optional[str] = Field(None, description="Additional context for the memory") + event_date: Optional[str] = Field(None, description="ISO format date when the event occurred") + + # Internal metrics (used by system but may not be exposed in API) + activation: Optional[float] = Field(None, description="Internal activation score") + + class Config: + json_schema_extra = { + "example": { + "id": "123e4567-e89b-12d3-a456-426614174000", + "text": "Alice works at Google on the AI team", + "fact_type": "world", + "context": "work info", + "event_date": "2024-01-15T10:30:00Z", + "activation": 0.95 + } + } + + +class SearchResult(BaseModel): + """ + Result from a search operation. + + Contains a list of matching memory facts and optional trace information + for debugging and transparency. + """ + results: List[MemoryFact] = Field(description="List of memory facts matching the query") + trace: Optional[Dict[str, Any]] = Field(None, description="Trace information for debugging") + + class Config: + json_schema_extra = { + "example": { + "results": [ + { + "id": "123e4567-e89b-12d3-a456-426614174000", + "text": "Alice works at Google on the AI team", + "fact_type": "world", + "context": "work info", + "event_date": "2024-01-15T10:30:00Z", + "activation": 0.95 + } + ], + "trace": { + "query": "What did Alice say about machine learning?", + "num_results": 1 + } + } + } + + +class ThinkResult(BaseModel): + """ + Result from a think operation. + + Contains the formulated answer, the facts it was based on (organized by type), + and any new opinions that were formed during the thinking process. + """ + text: str = Field(description="The formulated answer text") + based_on: Dict[str, List[MemoryFact]] = Field( + description="Facts used to formulate the answer, organized by type (world, agent, opinion)" + ) + new_opinions: List[str] = Field( + default_factory=list, + description="List of newly formed opinions during thinking" + ) + + class Config: + json_schema_extra = { + "example": { + "text": "Based on my knowledge, machine learning is being actively used in healthcare...", + "based_on": { + "world": [ + { + "id": "123e4567-e89b-12d3-a456-426614174000", + "text": "Machine learning is used in medical diagnosis", + "fact_type": "world", + "context": "healthcare", + "event_date": "2024-01-15T10:30:00Z" + } + ], + "agent": [], + "opinion": [] + }, + "new_opinions": [ + "Machine learning has great potential in healthcare" + ] + } + } + + +class Opinion(BaseModel): + """ + An opinion with confidence score. + + Opinions represent the agent's formed perspectives on topics, + with a confidence level indicating strength of belief. + """ + text: str = Field(description="The opinion text") + confidence: float = Field(description="Confidence score between 0.0 and 1.0") + + class Config: + json_schema_extra = { + "example": { + "text": "Machine learning has great potential in healthcare", + "confidence": 0.85 + } + } diff --git a/memora/memora/temporal_semantic_memory.py b/memora/memora/temporal_semantic_memory.py index 1576c5a8..09046d6c 100644 --- a/memora/memora/temporal_semantic_memory.py +++ b/memora/memora/temporal_semantic_memory.py @@ -28,6 +28,7 @@ from .utils import ( from .entity_resolver import EntityResolver from .operations import EmbeddingOperationsMixin, LinkOperationsMixin, ThinkOperationsMixin from .llm_wrapper import LLMConfig +from .response_models import SearchResult as SearchResultModel, ThinkResult, MemoryFact from .task_backend import TaskBackend, AsyncIOQueueBackend from .search.reranking import HeuristicReranker, CrossEncoderReranker @@ -159,6 +160,9 @@ class TemporalSemanticMemory( # concurrent puts to avoid connection pool exhaustion and reduce write contention self._put_semaphore = asyncio.Semaphore(5) + # initialize encoding eagerly to avoid delaying the first time + _get_tiktoken_encoding() + async def _handle_access_count_update(self, task_dict: Dict[str, Any]): """ Handler for access count update tasks. @@ -1028,7 +1032,7 @@ class TemporalSemanticMemory( enable_trace: bool = False, reranker: str = "cross-encoder", question_date: Optional[datetime] = None, - ) -> tuple[List[Dict[str, Any]], Optional[Any]]: + ) -> SearchResultModel: """ Search memories using N*4-way parallel retrieval (N fact types × 4 retrieval methods). @@ -1054,8 +1058,9 @@ class TemporalSemanticMemory( question_date: Optional date when question was asked (for temporal filtering) Returns: - Tuple of (results, trace) where results is a list of memory units - and trace is None (tracing removed) + SearchResultModel containing: + - results: List of MemoryFact objects + - trace: Optional trace information for debugging """ # Backpressure: limit concurrent searches to prevent overwhelming the database async with self._search_semaphore: @@ -1385,11 +1390,25 @@ class TemporalSemanticMemory( event_date = result["event_date"] result["event_date"] = event_date.isoformat() if hasattr(event_date, 'isoformat') else event_date + # Convert results to MemoryFact objects + memory_facts = [] + for result in top_results: + memory_facts.append(MemoryFact( + id=str(result.get("id")), + text=result.get("text"), + fact_type=result.get("fact_type", "world"), + context=result.get("context"), + event_date=result.get("event_date"), + activation=result.get("activation") + )) + # Finalize trace if enabled + trace_dict = None if tracer: trace = tracer.finalize(top_results) - return top_results, trace - return top_results, None + trace_dict = trace.to_dict() if trace else None + + return SearchResultModel(results=memory_facts, trace=trace_dict) except Exception as e: log_buffer.append(f"[SEARCH {search_id}] ERROR after {time.time() - search_start:.3f}s: {str(e)}") diff --git a/memora/memora/web/server.py b/memora/memora/web/server.py index 2db9b7de..c79a7f0d 100644 --- a/memora/memora/web/server.py +++ b/memora/memora/web/server.py @@ -11,14 +11,16 @@ import argparse from memora import TemporalSemanticMemory from memora.api import create_app +# Disable tokenizers parallelism to avoid warnings +os.environ["TOKENIZERS_PARALLELISM"] = "false" # Create app at module level (required for uvicorn import string) _memory = TemporalSemanticMemory( - db_url=os.getenv("DATABASE_URL"), - memory_llm_provider=os.getenv("MEMORY_LLM_PROVIDER", "groq"), - memory_llm_api_key=os.getenv("MEMORY_LLM_API_KEY"), - memory_llm_model=os.getenv("MEMORY_LLM_MODEL", "openai/gpt-oss-120b"), - memory_llm_base_url=os.getenv("MEMORY_LLM_BASE_URL") or None, + db_url=os.getenv("MEMORA_API_DATABASE_URL"), + memory_llm_provider=os.getenv("MEMORA_API_LLM_PROVIDER", "groq"), + memory_llm_api_key=os.getenv("MEMORA_API_LLM_API_KEY"), + memory_llm_model=os.getenv("MEMORA_API_LLM_MODEL", "openai/gpt-oss-120b"), + memory_llm_base_url=os.getenv("MEMORA_API_LLM_BASE_URL") or None, ) app = create_app(_memory) @@ -46,17 +48,6 @@ if __name__ == "__main__": args = parser.parse_args() - print("\n" + "=" * 80) - print("Memory Graph API Server") - print("=" * 80) - print(f"Host: {args.host}") - print(f"Port: {args.port}") - print(f"Reload: {args.reload}") - print(f"Workers: {args.workers}") - print(f"Log Level: {args.log_level}") - print("=" * 80 + "\n") - - # Always use import string for uvicorn (required for reload and workers) app_ref = "memora.web.server:app" # Prepare uvicorn config diff --git a/memora/tests/conftest.py b/memora/tests/conftest.py index 2971e0b0..cdc4d1dc 100644 --- a/memora/tests/conftest.py +++ b/memora/tests/conftest.py @@ -16,18 +16,18 @@ import asyncpg LOCAL_DB_URL = "postgresql://memora:memora_dev@localhost:5432/memora" -# Load environment variables from .env.local at the start of test session +# Load environment variables from .env at the start of test session def pytest_configure(config): """Load environment variables before running tests.""" - # Look for .env.local in the workspace root (two levels up from tests dir) - env_file = Path(__file__).parent.parent.parent / ".env.local" + # Look for .env in the workspace root (two levels up from tests dir) + env_file = Path(__file__).parent.parent.parent / ".env" if env_file.exists(): load_dotenv(env_file) else: print(f"Warning: {env_file} not found, tests may fail without proper configuration") - # Override DATABASE_URL to use local database - os.environ["DATABASE_URL"] = LOCAL_DB_URL + # Override MEMORA_API_DATABASE_URL to use local database + os.environ["MEMORA_API_DATABASE_URL"] = LOCAL_DB_URL @pytest.fixture(scope="session") @@ -53,10 +53,10 @@ async def memory(): """ mem = TemporalSemanticMemory( db_url=LOCAL_DB_URL, - memory_llm_provider=os.getenv("MEMORY_LLM_PROVIDER", "groq"), - memory_llm_api_key=os.getenv("MEMORY_LLM_API_KEY"), - memory_llm_model=os.getenv("MEMORY_LLM_MODEL", "openai/gpt-oss-120b"), - memory_llm_base_url=os.getenv("MEMORY_LLM_BASE_URL") or None, # Use None to get provider defaults + memory_llm_provider=os.getenv("MEMORA_API_LLM_PROVIDER", "groq"), + memory_llm_api_key=os.getenv("MEMORA_API_LLM_API_KEY"), + memory_llm_model=os.getenv("MEMORA_API_LLM_MODEL", "openai/gpt-oss-120b"), + memory_llm_base_url=os.getenv("MEMORA_API_LLM_BASE_URL") or None, # Use None to get provider defaults ) await mem.initialize() yield mem diff --git a/memora/tests/test_batch_chunking.py b/memora/tests/test_batch_chunking.py index afd66e38..6259a071 100644 --- a/memora/tests/test_batch_chunking.py +++ b/memora/tests/test_batch_chunking.py @@ -9,11 +9,11 @@ import os async def test_large_batch_auto_chunks(): """Test that large batches are automatically split into smaller chunks.""" memory = TemporalSemanticMemory( - db_url=os.getenv("DATABASE_URL"), - memory_llm_provider=os.getenv("MEMORY_LLM_PROVIDER", "groq"), - memory_llm_api_key=os.getenv("MEMORY_LLM_API_KEY"), - memory_llm_model=os.getenv("MEMORY_LLM_MODEL", "openai/gpt-oss-120b"), - memory_llm_base_url=os.getenv("MEMORY_LLM_BASE_URL") or None + db_url=os.getenv("MEMORA_API_DATABASE_URL"), + memory_llm_provider=os.getenv("MEMORA_API_LLM_PROVIDER", "groq"), + memory_llm_api_key=os.getenv("MEMORA_API_LLM_API_KEY"), + memory_llm_model=os.getenv("MEMORA_API_LLM_MODEL", "openai/gpt-oss-120b"), + memory_llm_base_url=os.getenv("MEMORA_API_LLM_BASE_URL") or None ) agent_id = "test_chunking_agent" @@ -52,11 +52,11 @@ async def test_large_batch_auto_chunks(): async def test_small_batch_no_chunking(): """Test that small batches are not chunked.""" memory = TemporalSemanticMemory( - db_url=os.getenv("DATABASE_URL"), - memory_llm_provider=os.getenv("MEMORY_LLM_PROVIDER", "groq"), - memory_llm_api_key=os.getenv("MEMORY_LLM_API_KEY"), - memory_llm_model=os.getenv("MEMORY_LLM_MODEL", "openai/gpt-oss-120b"), - memory_llm_base_url=os.getenv("MEMORY_LLM_BASE_URL") or None + db_url=os.getenv("MEMORA_API_DATABASE_URL"), + memory_llm_provider=os.getenv("MEMORA_API_LLM_PROVIDER", "groq"), + memory_llm_api_key=os.getenv("MEMORA_API_LLM_API_KEY"), + memory_llm_model=os.getenv("MEMORA_API_LLM_MODEL", "openai/gpt-oss-120b"), + memory_llm_base_url=os.getenv("MEMORA_API_LLM_BASE_URL") or None ) agent_id = "test_no_chunking_agent" diff --git a/memora/tests/test_search_trace.py b/memora/tests/test_search_trace.py index 3b01c880..75d1205f 100644 --- a/memora/tests/test_search_trace.py +++ b/memora/tests/test_search_trace.py @@ -32,7 +32,7 @@ async def test_search_with_trace(memory): ) # Search with tracing enabled - results, trace = await memory.search_async( + search_result = await memory.search_async( agent_id=agent_id, query="Who works at Google?", fact_type=["world"], @@ -42,87 +42,62 @@ async def test_search_with_trace(memory): ) # Verify results - assert len(results) > 0, "Should have search results" + assert len(search_result.results) > 0, "Should have search results" # Verify trace object - assert trace is not None, "Trace should not be None when enable_trace=True" - assert isinstance(trace, SearchTrace), "Trace should be SearchTrace instance" + assert search_result.trace is not None, "Trace should not be None when enable_trace=True" + # Trace is now a dict + trace = search_result.trace # Verify query info - assert trace.query.query_text == "Who works at Google?" - assert trace.query.thinking_budget == 20 - assert trace.query.max_tokens == 512 - assert len(trace.query.query_embedding) > 0, "Query embedding should be populated" + assert trace["query"]["query_text"] == "Who works at Google?" + assert trace["query"]["thinking_budget"] == 20 + assert trace["query"]["max_tokens"] == 512 + assert len(trace["query"]["query_embedding"]) > 0, "Query embedding should be populated" # Verify entry points - assert len(trace.entry_points) > 0, "Should have entry points" - for ep in trace.entry_points: - assert ep.node_id, "Entry point should have node_id" - assert ep.text, "Entry point should have text" - assert 0.0 <= ep.similarity_score <= 1.0, "Similarity should be in [0, 1]" + assert len(trace["entry_points"]) > 0, "Should have entry points" + for ep in trace["entry_points"]: + assert ep["node_id"], "Entry point should have node_id" + assert ep["text"], "Entry point should have text" + assert 0.0 <= ep["similarity_score"] <= 1.0, "Similarity should be in [0, 1]" # Verify visits - assert len(trace.visits) > 0, "Should have visited nodes" - for visit in trace.visits: - assert visit.node_id, "Visit should have node_id" - assert visit.text, "Visit should have text" - assert visit.weights.final_weight >= 0, "Weight should be non-negative" + assert len(trace["visits"]) > 0, "Should have visited nodes" + for visit in trace["visits"]: + assert visit["node_id"], "Visit should have node_id" + assert visit["text"], "Visit should have text" + assert visit["weights"]["final_weight"] >= 0, "Weight should be non-negative" # Entry points should have no parent - if visit.is_entry_point: - assert visit.parent_node_id is None - assert visit.link_type is None + if visit["is_entry_point"]: + assert visit["parent_node_id"] is None + assert visit["link_type"] is None else: # Non-entry points should have parent info (unless they're isolated) # But we allow None parent if the node was reached differently pass # Verify summary - assert trace.summary.total_nodes_visited == len(trace.visits) - assert trace.summary.results_returned == len(results) - assert trace.summary.budget_used <= trace.query.thinking_budget - assert trace.summary.total_duration_seconds > 0 + assert trace["summary"]["total_nodes_visited"] == len(trace["visits"]) + assert trace["summary"]["results_returned"] == len(search_result.results) + assert trace["summary"]["budget_used"] <= trace["query"]["thinking_budget"] + assert trace["summary"]["total_duration_seconds"] > 0 # Verify phase metrics - assert len(trace.summary.phase_metrics) > 0, "Should have phase metrics" - phase_names = {pm.phase_name for pm in trace.summary.phase_metrics} + assert len(trace["summary"]["phase_metrics"]) > 0, "Should have phase metrics" + phase_names = {pm["phase_name"] for pm in trace["summary"]["phase_metrics"]} assert "generate_query_embedding" in phase_names assert "parallel_retrieval" in phase_names # New modular architecture assert "rrf_merge" in phase_names # New modular architecture assert "reranking" in phase_names # New modular architecture - # Test JSON export - json_str = trace.to_json() - assert json_str, "Should be able to export to JSON" - assert "query" in json_str - assert "visits" in json_str - assert "summary" in json_str - - # Test dict export - trace_dict = trace.to_dict() - assert isinstance(trace_dict, dict) - assert "query" in trace_dict - assert "visits" in trace_dict - - # Test helper methods - if len(trace.visits) > 0: - first_visit = trace.visits[0] - found_visit = trace.get_visit_by_node_id(first_visit.node_id) - assert found_visit is not None - assert found_visit.node_id == first_visit.node_id - - # Test get_entry_point_nodes - entry_point_visits = trace.get_entry_point_nodes() - assert len(entry_point_visits) > 0 - for epv in entry_point_visits: - assert epv.is_entry_point - print("\n✓ Search trace test passed!") - print(f" - Query: {trace.query.query_text}") - print(f" - Entry points: {len(trace.entry_points)}") - print(f" - Nodes visited: {trace.summary.total_nodes_visited}") - print(f" - Nodes pruned: {trace.summary.total_nodes_pruned}") - print(f" - Results returned: {trace.summary.results_returned}") - print(f" - Duration: {trace.summary.total_duration_seconds:.3f}s") + print(f" - Query: {trace['query']['query_text']}") + print(f" - Entry points: {len(trace['entry_points'])}") + print(f" - Nodes visited: {trace['summary']['total_nodes_visited']}") + print(f" - Nodes pruned: {trace['summary']['total_nodes_pruned']}") + print(f" - Results returned: {trace['summary']['results_returned']}") + print(f" - Duration: {trace['summary']['total_duration_seconds']:.3f}s") finally: # Cleanup @@ -144,7 +119,7 @@ async def test_search_without_trace(memory): ) # Search without tracing - results, trace = await memory.search_async( + search_result = await memory.search_async( agent_id=agent_id, query="test", fact_type=["world"], @@ -154,8 +129,8 @@ async def test_search_without_trace(memory): ) # Verify trace is None - assert trace is None, "Trace should be None when enable_trace=False" - assert isinstance(results, list), "Results should still be a list" + assert search_result.trace is None, "Trace should be None when enable_trace=False" + assert isinstance(search_result.results, list), "Results should still be a list" print("\n✓ Search without trace test passed!") diff --git a/memora/tests/test_think.py b/memora/tests/test_think.py index fbc2821b..b16a3410 100644 --- a/memora/tests/test_think.py +++ b/memora/tests/test_think.py @@ -41,17 +41,15 @@ async def test_think_opinion_consistency(memory): ) print(f"\n=== First Think Call ===") - print(f"Answer: {result1['text']}") - print(f"New opinions formed: {len(result1.get('new_opinions', []))}") - for opinion in result1.get('new_opinions', []): - print(f" - {opinion['text']} (confidence: {opinion['confidence']:.2f})") + print(f"Answer: {result1.text}") + print(f"New opinions formed: {len(result1.new_opinions)}") # Verify we got an answer - assert result1['text'], "First think call should return an answer" - assert 'based_on' in result1, "Should return based_on facts" + assert result1.text, "First think call should return an answer" + assert result1.based_on, "Should return based_on facts" # Verify opinions were formed - new_opinions_count = len(result1.get('new_opinions', [])) + new_opinions_count = len(result1.new_opinions) print(f"\nNew opinions formed: {new_opinions_count}") # Wait for background opinion PUT tasks to complete @@ -91,23 +89,23 @@ async def test_think_opinion_consistency(memory): ) print(f"\n=== Second Think Call ===") - print(f"Answer: {result2['text']}") - print(f"Existing opinions used: {len(result2['based_on'].get('opinion', []))}") - for opinion in result2['based_on'].get('opinion', []): - print(f" - {opinion['text']}") - print(f"New opinions formed: {len(result2.get('new_opinions', []))}") + print(f"Answer: {result2.text}") + print(f"Existing opinions used: {len(result2.based_on.get('opinion', []))}") + for opinion in result2.based_on.get('opinion', []): + print(f" - {opinion.text}") + print(f"New opinions formed: {len(result2.new_opinions)}") # Verify second call also got an answer - assert result2['text'], "Second think call should return an answer" + assert result2.text, "Second think call should return an answer" # Verify second call used the stored opinions (if any were stored) if len(stored_opinions) > 0: - assert len(result2['based_on'].get('opinion', [])) > 0, "Second call should retrieve stored opinions" + assert len(result2.based_on.get('opinion', [])) > 0, "Second call should retrieve stored opinions" # The responses should be consistent (both should mention the same person as more reliable) # We'll do a basic check that they're not contradictory - text1_lower = result1['text'].lower() - text2_lower = result2['text'].lower() + text1_lower = result1.text.lower() + text2_lower = result2.text.lower() print(f"\n=== Consistency Check ===") @@ -149,11 +147,11 @@ async def test_think_without_prior_context(memory): ) print(f"\n=== Think Without Context ===") - print(f"Answer: {result['text']}") + print(f"Answer: {result.text}") # Should still return an answer (even if it says it doesn't have enough info) - assert result['text'], "Should return some answer" - assert 'based_on' in result, "Should return based_on structure" + assert result.text, "Should return some answer" + assert result.based_on, "Should return based_on structure" if __name__ == "__main__": diff --git a/openapi.json b/openapi.json index cb50e030..f501c010 100644 --- a/openapi.json +++ b/openapi.json @@ -340,16 +340,9 @@ { "name": "agent_id", "in": "query", - "required": false, + "required": true, "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], + "type": "string", "title": "Agent Id" } }, @@ -872,11 +865,6 @@ ], "title": "Content Hash" }, - "metadata": { - "additionalProperties": true, - "type": "object", - "title": "Metadata" - }, "created_at": { "type": "string", "title": "Created At" @@ -896,7 +884,6 @@ "agent_id", "original_text", "content_hash", - "metadata", "created_at", "updated_at", "memory_unit_count" @@ -909,9 +896,6 @@ "created_at": "2024-01-15T10:30:00Z", "id": "session_1", "memory_unit_count": 15, - "metadata": { - "source": "conversation" - }, "original_text": "Full document text here...", "updated_at": "2024-01-15T10:30:00Z" } @@ -1042,9 +1026,6 @@ "created_at": "2024-01-15T10:30:00Z", "id": "session_1", "memory_unit_count": 15, - "metadata": { - "source": "conversation" - }, "text_length": 5420, "updated_at": "2024-01-15T10:30:00Z" } @@ -1144,25 +1125,6 @@ "event_date": "2024-01-15T10:30:00Z" } }, - "OpinionItem": { - "properties": { - "text": { - "type": "string", - "title": "Text" - }, - "confidence": { - "type": "number", - "title": "Confidence" - } - }, - "type": "object", - "required": [ - "text", - "confidence" - ], - "title": "OpinionItem", - "description": "Model for an opinion with confidence score." - }, "SearchRequest": { "properties": { "query": { @@ -1170,10 +1132,17 @@ "title": "Query" }, "fact_type": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Fact Type" }, "agent_id": { @@ -1215,8 +1184,7 @@ }, "type": "object", "required": [ - "query", - "fact_type" + "query" ], "title": "SearchRequest", "description": "Request model for search endpoint.", @@ -1238,8 +1206,7 @@ "properties": { "results": { "items": { - "additionalProperties": true, - "type": "object" + "$ref": "#/components/schemas/SearchResult" }, "type": "array", "title": "Results" @@ -1266,9 +1233,12 @@ "example": { "results": [ { + "activation": 0.95, + "context": "work info", + "event_date": "2024-01-15T10:30:00Z", "id": "123e4567-e89b-12d3-a456-426614174000", - "score": 0.95, - "text": "Alice works at Google on the AI team" + "text": "Alice works at Google on the AI team", + "type": "world" } ], "trace": { @@ -1278,6 +1248,152 @@ } } }, + "SearchResult": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "text": { + "type": "string", + "title": "Text" + }, + "type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Type" + }, + "activation": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Activation" + }, + "context": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Context" + }, + "event_date": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Event Date" + } + }, + "type": "object", + "required": [ + "id", + "text" + ], + "title": "SearchResult", + "description": "Single search result item.", + "example": { + "context": "work info", + "event_date": "2024-01-15T10:30:00Z", + "id": "123e4567-e89b-12d3-a456-426614174000", + "text": "Alice works at Google on the AI team", + "type": "world" + } + }, + "ThinkFact": { + "properties": { + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Id" + }, + "text": { + "type": "string", + "title": "Text" + }, + "type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Type" + }, + "activation": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Activation" + }, + "context": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Context" + }, + "event_date": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Event Date" + } + }, + "type": "object", + "required": [ + "text" + ], + "title": "ThinkFact", + "description": "A fact used in think response.", + "example": { + "context": "healthcare discussion", + "event_date": "2024-01-15T10:30:00Z", + "id": "123e4567-e89b-12d3-a456-426614174000", + "text": "AI is used in healthcare", + "type": "world" + } + }, "ThinkRequest": { "properties": { "query": { @@ -1314,19 +1430,16 @@ "title": "Text" }, "based_on": { - "additionalProperties": { - "items": { - "additionalProperties": true, - "type": "object" - }, - "type": "array" + "items": { + "$ref": "#/components/schemas/ThinkFact" }, - "type": "object", - "title": "Based On" + "type": "array", + "title": "Based On", + "default": [] }, "new_opinions": { "items": { - "$ref": "#/components/schemas/OpinionItem" + "type": "string" }, "type": "array", "title": "New Opinions", @@ -1335,38 +1448,28 @@ }, "type": "object", "required": [ - "text", - "based_on" + "text" ], "title": "ThinkResponse", "description": "Response model for think endpoint.", "example": { - "based_on": { - "agent": [ - { - "score": 0.85, - "text": "I discussed AI applications last week" - } - ], - "opinion": [ - { - "score": 0.8, - "text": "I believe AI should be used ethically" - } - ], - "world": [ - { - "score": 0.9, - "text": "AI is used in healthcare" - } - ] - }, - "new_opinions": [ + "based_on": [ { - "confidence": 0.95, - "text": "AI has great potential when used responsibly" + "activation": 0.9, + "id": "123", + "text": "AI is used in healthcare", + "type": "world" + }, + { + "activation": 0.85, + "id": "456", + "text": "I discussed AI applications last week", + "type": "agent" } ], + "new_opinions": [ + "AI has great potential when used responsibly" + ], "text": "Based on my understanding, AI is a transformative technology..." } }, diff --git a/pyproject.toml b/pyproject.toml index ea1fd490..d170fc27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.uv.workspace] -members = ["memora", "benchmarks", "memora-dev", "memora-cli"] +members = ["memora", "memora-dev", "memora-dev/benchmarks"] [tool.uv] dev-dependencies = [] diff --git a/scripts/benchmarks/run-locomo.sh b/scripts/benchmarks/run-locomo.sh index aafd5016..f34d718b 100755 --- a/scripts/benchmarks/run-locomo.sh +++ b/scripts/benchmarks/run-locomo.sh @@ -40,4 +40,4 @@ set -a source "$ENV_FILE" set +a -uv run python benchmarks/locomo/locomo_benchmark.py "${ARGS[@]}" +uv run python memora-dev/benchmarks/locomo/locomo_benchmark.py "${ARGS[@]}" diff --git a/scripts/benchmarks/run-longmemeval.sh b/scripts/benchmarks/run-longmemeval.sh index 326e48a5..cfe8805f 100755 --- a/scripts/benchmarks/run-longmemeval.sh +++ b/scripts/benchmarks/run-longmemeval.sh @@ -40,4 +40,4 @@ set -a source "$ENV_FILE" set +a -uv run python benchmarks/longmemeval/longmemeval_benchmark.py "${ARGS[@]}" +uv run python memora-dev/benchmarks/longmemeval/longmemeval_benchmark.py "${ARGS[@]}" diff --git a/scripts/benchmarks/start-visualizer.sh b/scripts/benchmarks/start-visualizer.sh index 80b8b8d9..5fc43256 100755 --- a/scripts/benchmarks/start-visualizer.sh +++ b/scripts/benchmarks/start-visualizer.sh @@ -8,4 +8,4 @@ echo "" echo "Server will be available at: http://localhost:8001" echo "" -uv run python benchmarks/visualizer/main.py +uv run python memora-dev/benchmarks/visualizer/main.py diff --git a/scripts/start-server.sh b/scripts/dev/start-api.sh similarity index 84% rename from scripts/start-server.sh rename to scripts/dev/start-api.sh index c133212e..01250927 100755 --- a/scripts/start-server.sh +++ b/scripts/dev/start-api.sh @@ -1,22 +1,13 @@ #!/bin/bash set -e -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/../.." # Parse arguments -ENV_MODE="local" SERVER_ARGS=() while [[ $# -gt 0 ]]; do case $1 in - --env) - ENV_MODE="$2" - if [[ "$ENV_MODE" != "local" && "$ENV_MODE" != "dev" ]]; then - echo "Error: --env must be 'local' or 'dev'" - exit 1 - fi - shift 2 - ;; --help|-h) echo "Usage: $0 [--env local|dev] [uvicorn options...]" echo "" @@ -50,13 +41,12 @@ while [[ $# -gt 0 ]]; do done # Source environment file -ENV_FILE=".env.${ENV_MODE}" +ENV_FILE=".env" if [ ! -f "$ENV_FILE" ]; then - echo "Error: Environment file $ENV_FILE not found" + echo "Error: Environment file $ENV_FILE not found at project root." exit 1 fi -echo "🚀 Starting Memory API Server with '${ENV_MODE}' environment..." echo "📄 Loading environment from $ENV_FILE" echo "" diff --git a/scripts/start-control-plane.sh b/scripts/dev/start-control-plane.sh similarity index 50% rename from scripts/start-control-plane.sh rename to scripts/dev/start-control-plane.sh index 3d02de94..2344a2c6 100755 --- a/scripts/start-control-plane.sh +++ b/scripts/dev/start-control-plane.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -cd "$(dirname "$0")/../control-plane" +cd "$(dirname "$0")/../../memora-control-plane" # Parse arguments PORT=3000 @@ -31,26 +31,30 @@ while [[ $# -gt 0 ]]; do esac done -# Check if .env.local exists -if [ ! -f ".env.local" ]; then - echo "⚠️ Warning: .env.local not found" - echo "Creating from .env.local.example..." - if [ -f ".env.local.example" ]; then - cp .env.local.example .env.local - echo "✅ Created .env.local" - echo "📝 Please edit .env.local if you need to change the DATAPLANE_API_URL" - echo "" - else - echo "❌ Error: .env.local.example not found" - exit 1 - fi +# Check if .env exists in workspace root +ROOT_DIR="$(dirname "$0")/../.." +if [ ! -f "$ROOT_DIR/.env" ]; then + echo "⚠️ Warning: .env not found in workspace root" + echo "📝 Please create a .env file if you need to set MEMORA_CP_DATAPLANE_API_URL" + echo " Default will use http://localhost:8080" + echo "" fi echo "🚀 Starting Control Plane (Next.js dev server)..." -echo "📄 Loading environment from .env.local" +if [ -f "$ROOT_DIR/.env" ]; then + echo "📄 Loading environment from $ROOT_DIR/.env" + # Load env vars from root .env file + set -a + source "$ROOT_DIR/.env" + set +a +fi echo "" echo "Control plane will be available at: http://localhost:${PORT}" echo "" -# Set the port and run dev server -PORT=$PORT npm run dev +# Map prefixed env vars to Next.js standard vars +export HOSTNAME="${MEMORA_CP_HOSTNAME:-0.0.0.0}" +export PORT="${MEMORA_CP_PORT:-$PORT}" + +# Run dev server +npm run dev diff --git a/scripts/erase-local-db.sh b/scripts/erase-local-db.sh deleted file mode 100755 index 30d0ce3e..00000000 --- a/scripts/erase-local-db.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -e - -cd "$(dirname "$0")/.." - -echo "🛑 Stopping and erasing local PostgreSQL..." -echo "" - -# Stop and remove containers, networks, volumes -cd local-db -docker-compose down -v - -echo "" -echo "✅ Local database has been stopped and all data erased!" -echo "" diff --git a/scripts/migrate-db.sh b/scripts/migrate-db.sh deleted file mode 100755 index f6680429..00000000 --- a/scripts/migrate-db.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -set -e - -cd "$(dirname "$0")/../memora" - -echo "🔄 Database Migration Script" -echo "============================" -echo "" - -# Check if DATABASE_URL is set -if [ -z "$DATABASE_URL" ]; then - echo "⚠️ DATABASE_URL environment variable is not set!" - echo "" - echo "Please set DATABASE_URL to your PostgreSQL connection string." - echo "Example:" - echo " export DATABASE_URL=\"postgresql://user:password@localhost:5432/dbname\"" - echo "" - echo "For local development:" - echo " export DATABASE_URL=\"postgresql://memora:memora_dev@localhost:5432/memora\"" - echo "" - exit 1 -fi - -echo "📊 Database: $DATABASE_URL" -echo "" - -# Check current migration status -echo "📋 Current migration status:" -echo "----------------------------" -uv run alembic current || true -echo "" - -# Show pending migrations -echo "🔍 Checking for pending migrations..." -echo "-------------------------------------" -PENDING=$(uv run alembic heads 2>&1) -CURRENT=$(uv run alembic current 2>&1 | grep -o '[a-f0-9]\{12\}' | head -n 1 || echo "none") - -if echo "$CURRENT" | grep -q "none"; then - echo "⚠️ Database is not initialized. Running all migrations..." -else - echo "Current revision: $CURRENT" -fi -echo "" - -# Run migrations -echo "🚀 Running migrations to latest version..." -echo "-------------------------------------------" -uv run alembic upgrade head - -echo "" -echo "✅ Database migrations completed successfully!" -echo "" - -# Show final status -echo "📊 Final migration status:" -echo "--------------------------" -uv run alembic current -echo "" - -echo "✨ Database is now up to date!" -echo "" diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 00000000..e3bce381 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,147 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to print colored output +print_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if version is provided +if [ -z "$1" ]; then + print_error "Usage: $0 " + print_info "Example: $0 0.2.0" + exit 1 +fi + +VERSION=$1 + +# Validate version format (semantic versioning) +if ! [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + print_error "Invalid version format. Please use semantic versioning (e.g., 0.2.0)" + exit 1 +fi + +print_info "Starting release process for version $VERSION" + +# Check if we're on main branch +CURRENT_BRANCH=$(git branch --show-current) +if [ "$CURRENT_BRANCH" != "main" ]; then + print_warn "You are not on the main branch (current: $CURRENT_BRANCH)" + read -p "Do you want to continue? (y/n) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + print_error "Release cancelled" + exit 1 + fi +fi + +# Check if working directory is clean +if [[ -n $(git status -s) ]]; then + print_error "Working directory is not clean. Please commit or stash your changes." + git status -s + exit 1 +fi + +# Check if tag already exists +if git rev-parse "v$VERSION" >/dev/null 2>&1; then + print_error "Tag v$VERSION already exists" + exit 1 +fi + +print_info "Updating version in all components..." + +# Update Python packages +PYTHON_PACKAGES=("memora" "benchmarks" "memora-dev") +for package in "${PYTHON_PACKAGES[@]}"; do + PYPROJECT_FILE="$package/pyproject.toml" + if [ -f "$PYPROJECT_FILE" ]; then + print_info "Updating $PYPROJECT_FILE" + sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" "$PYPROJECT_FILE" + rm "${PYPROJECT_FILE}.bak" + else + print_warn "File $PYPROJECT_FILE not found, skipping" + fi +done + +# Update Rust CLI +CARGO_FILE="memora-cli/Cargo.toml" +if [ -f "$CARGO_FILE" ]; then + print_info "Updating $CARGO_FILE" + sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" "$CARGO_FILE" + rm "${CARGO_FILE}.bak" +else + print_warn "File $CARGO_FILE not found, skipping" +fi + +# Update Helm chart +HELM_CHART_FILE="helm/memora/Chart.yaml" +if [ -f "$HELM_CHART_FILE" ]; then + print_info "Updating $HELM_CHART_FILE" + sed -i.bak "s/^version: .*/version: $VERSION/" "$HELM_CHART_FILE" + sed -i.bak "s/^appVersion: .*/appVersion: \"$VERSION\"/" "$HELM_CHART_FILE" + rm "${HELM_CHART_FILE}.bak" +else + print_warn "File $HELM_CHART_FILE not found, skipping" +fi + +# Update Control Plane package.json +CONTROL_PLANE_PKG="memora-control-plane/package.json" +if [ -f "$CONTROL_PLANE_PKG" ]; then + print_info "Updating $CONTROL_PLANE_PKG" + sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$CONTROL_PLANE_PKG" + rm "${CONTROL_PLANE_PKG}.bak" +else + print_warn "File $CONTROL_PLANE_PKG not found, skipping" +fi + +# Show changes +print_info "Changes to be committed:" +git diff + +# Confirm changes +echo +read -p "Do you want to commit these changes and create tag v$VERSION? (y/n) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + print_error "Release cancelled. Rolling back changes..." + git checkout . + exit 1 +fi + +# Commit changes +print_info "Committing version changes..." +git add -A +git commit -m "Release v$VERSION + +- Update version to $VERSION in all components +- Python packages: memora, benchmarks, memora-dev +- Rust CLI: memora-cli +- Control Plane: memora-control-plane +- Helm chart" + +# Create tag +print_info "Creating tag v$VERSION..." +git tag -a "v$VERSION" -m "Release v$VERSION" + +# Push changes +print_info "Pushing changes and tag to remote..." +git push origin "$CURRENT_BRANCH" +git push origin "v$VERSION" + +print_info "✅ Release v$VERSION completed successfully!" +print_info "GitHub Actions will now build the release artifacts." +print_info "Tag: v$VERSION" diff --git a/scripts/start-local-db.sh b/scripts/start-local-db.sh deleted file mode 100755 index dcd7f35f..00000000 --- a/scripts/start-local-db.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -set -e - -cd "$(dirname "$0")/.." - -echo "🚀 Starting local PostgreSQL..." -echo "" - -# Start docker compose -cd local-db -docker-compose up -d - -echo "" -echo "⏳ Waiting for PostgreSQL to be ready..." -until docker exec memora-postgres pg_isready -U memora > /dev/null 2>&1; do - sleep 1 -done - -echo "" -echo "✅ PostgreSQL is ready!" -echo "" - -# Initialize database schema -cd .. -export DATABASE_URL="postgresql://memora:memora_dev@localhost:5432/memora" - -echo "📊 Running database migrations..." -cd memora -uv run alembic upgrade head -cd .. - -echo "" -echo "✅ Database initialized successfully!" -echo "" -echo "📊 Connection Info:" -echo " Host: localhost" -echo " Port: 5432" -echo " Database: memora" -echo " User: memora" -echo " Password: memora_dev" -echo "" -echo "🛑 To stop and clean up:" -echo " ./scripts/erase-local-db.sh" -echo "" diff --git a/standalone/Dockerfile b/standalone/Dockerfile index e07f184b..7bbe36c2 100644 --- a/standalone/Dockerfile +++ b/standalone/Dockerfile @@ -1,11 +1,11 @@ FROM node:20-alpine AS control-plane-builder # Build control plane -WORKDIR /app/control-plane -COPY control-plane/package*.json ./ +WORKDIR /app/memora-control-plane +COPY memora-control-plane/package*.json ./ RUN npm ci -COPY control-plane/ ./ +COPY memora-control-plane/ ./ # Set env to skip font optimization during build ENV NEXT_TELEMETRY_DISABLED=1 RUN npm run build || (echo "Build failed, retrying..." && npm run build) @@ -16,9 +16,7 @@ FROM python:3.12-slim AS dataplane-source WORKDIR /build COPY pyproject.toml uv.lock ./ COPY memora/ ./memora/ -COPY benchmarks/ ./benchmarks/ COPY memora-dev/ ./memora-dev/ -COPY memora-cli/ ./memora-cli/ # Final runtime image FROM python:3.12-slim @@ -55,8 +53,10 @@ COPY --from=dataplane-source /build /app RUN cd /app && uv sync --frozen # Copy control plane from builder -COPY --from=control-plane-builder /app/control-plane/.next/standalone /app/control-plane -COPY --from=control-plane-builder /app/control-plane/.next/static /app/control-plane/.next/static +COPY --from=control-plane-builder /app/memora-control-plane/.next/standalone /app/memora-control-plane +COPY --from=control-plane-builder /app/memora-control-plane/.next/static /app/memora-control-plane/.next/static +COPY memora-control-plane/start-server.sh /app/memora-control-plane/start-server.sh +RUN chmod +x /app/memora-control-plane/start-server.sh # Copy standalone configuration COPY standalone/supervisord.conf /etc/supervisor/conf.d/supervisord.conf diff --git a/standalone/QUICKSTART.md b/standalone/QUICKSTART.md index cf1da00a..05d04cb1 100644 --- a/standalone/QUICKSTART.md +++ b/standalone/QUICKSTART.md @@ -67,9 +67,11 @@ docker-compose down -v Set in `docker-compose.yml` or pass with `-e`: -- `EMBEDDING_MODEL_NAME` - Sentence transformer model (default: sentence-transformers/all-MiniLM-L6-v2) -- `EMBEDDING_DIM` - Embedding dimension (default: 384) -- `OPENAI_API_KEY` - Optional OpenAI API key +- `MEMORA_API_LLM_PROVIDER` - LLM provider (openai, groq, ollama, none) (default: none) +- `MEMORA_API_LLM_API_KEY` - API key for LLM provider +- `MEMORA_API_LLM_MODEL` - LLM model name (default: openai/gpt-oss-120b) +- `MEMORA_API_LLM_BASE_URL` - Optional custom LLM endpoint +- `MEMORA_CP_DATAPLANE_API_URL` - Dataplane API URL (default: http://localhost:8080) ## Troubleshooting diff --git a/standalone/README.md b/standalone/README.md index 827eecf2..6392442c 100644 --- a/standalone/README.md +++ b/standalone/README.md @@ -66,8 +66,9 @@ docker run -p 3000:3000 -p 8080:8080 \ With custom environment variables: ```bash docker run -p 3000:3000 -p 8080:8080 \ - -e OPENAI_API_KEY=your-key \ - -e EMBEDDING_MODEL_NAME=custom-model \ + -e MEMORA_API_LLM_PROVIDER=groq \ + -e MEMORA_API_LLM_API_KEY=your-key \ + -e MEMORA_API_LLM_MODEL=openai/gpt-oss-120b \ memora-standalone:latest ``` @@ -96,11 +97,12 @@ The `init.sh` script handles: | Variable | Default | Description | |----------|---------|-------------| -| `DATABASE_URL` | `postgresql://postgres:postgres@localhost:5432/memora` | PostgreSQL connection string | -| `DATAPLANE_API_URL` | `http://localhost:8080` | Dataplane API URL for control plane | -| `EMBEDDING_MODEL_NAME` | `sentence-transformers/all-MiniLM-L6-v2` | Sentence transformer model | -| `EMBEDDING_DIM` | `384` | Embedding dimension | -| `OPENAI_API_KEY` | - | Optional OpenAI API key | +| `MEMORA_API_DATABASE_URL` | `postgresql://postgres:postgres@localhost:5432/memora` | PostgreSQL connection string | +| `MEMORA_CP_DATAPLANE_API_URL` | `http://localhost:8080` | Dataplane API URL for control plane | +| `MEMORA_API_LLM_PROVIDER` | `none` | LLM provider (openai, groq, ollama, none) | +| `MEMORA_API_LLM_API_KEY` | - | API key for LLM provider | +| `MEMORA_API_LLM_MODEL` | `openai/gpt-oss-120b` | LLM model name | +| `MEMORA_API_LLM_BASE_URL` | - | Optional custom LLM endpoint | ## Logs diff --git a/standalone/init.sh b/standalone/init.sh index 10609722..831f2b58 100755 --- a/standalone/init.sh +++ b/standalone/init.sh @@ -27,8 +27,11 @@ done echo "📊 Setting up database..." su - postgres -c "psql -tc \"SELECT 1 FROM pg_database WHERE datname = 'memora'\" | grep -q 1 || psql -c 'CREATE DATABASE memora;'" -# Run migrations -echo "🔄 Running database migrations..." +# Run initial migrations +# Note: The API also runs migrations automatically on startup. +# We run them here during initialization to ensure the database +# schema is ready before handing off to supervisord. +echo "🔄 Running initial database migrations..." cd /app/memora # Export environment variables diff --git a/standalone/supervisord.conf b/standalone/supervisord.conf index 32b16c56..809d6b87 100644 --- a/standalone/supervisord.conf +++ b/standalone/supervisord.conf @@ -18,7 +18,7 @@ priority=1 [program:dataplane] command=/app/.venv/bin/python -m memora.web.server --host 0.0.0.0 --port 8080 directory=/app/memora -environment=PATH="/app/.venv/bin:%(ENV_PATH)s",DATABASE_URL="postgresql://postgres:postgres@localhost:5432/memora",EMBEDDING_MODEL_NAME="sentence-transformers/all-MiniLM-L6-v2",EMBEDDING_DIM="384" +environment=PATH="/app/.venv/bin:%(ENV_PATH)s",MEMORA_API_DATABASE_URL="postgresql://postgres:postgres@localhost:5432/memora",MEMORA_API_LLM_PROVIDER="none" autostart=true autorestart=true stdout_logfile=/dev/stdout @@ -28,10 +28,10 @@ stderr_logfile_maxbytes=0 startsecs=10 priority=10 -[program:control-plane] -command=/usr/bin/node /app/control-plane/server.js -directory=/app/control-plane -environment=NODE_ENV="production",PORT="3000",HOSTNAME="0.0.0.0",DATAPLANE_API_URL="http://localhost:8080" +[program:memora-control-plane] +command=/app/memora-control-plane/start-server.sh +directory=/app/memora-control-plane +environment=NODE_ENV="production",MEMORA_CP_PORT="3000",MEMORA_CP_HOSTNAME="0.0.0.0",MEMORA_CP_DATAPLANE_API_URL="http://localhost:8080" autostart=true autorestart=true stdout_logfile=/dev/stdout diff --git a/uv.lock b/uv.lock index fc37f64e..9c118b20 100644 --- a/uv.lock +++ b/uv.lock @@ -10,7 +10,6 @@ resolution-markers = [ members = [ "benchmarks", "memora", - "memora-cli", "memora-dev", ] @@ -46,11 +45,11 @@ wheels = [ [[package]] name = "annotated-doc" -version = "0.0.3" +version = "0.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/a6/dc46877b911e40c00d395771ea710d5e77b6de7bacd5fdcd78d70cc5a48f/annotated_doc-0.0.3.tar.gz", hash = "sha256:e18370014c70187422c33e945053ff4c286f453a984eba84d0dbfa0c935adeda", size = 5535 } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/b7/cf592cb5de5cb3bade3357f8d2cf42bf103bbe39f459824b4939fd212911/annotated_doc-0.0.3-py3-none-any.whl", hash = "sha256:348ec6664a76f1fd3be81f43dffbee4c7e8ce931ba71ec67cc7f4ade7fbbb580", size = 5488 }, + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303 }, ] [[package]] @@ -219,7 +218,7 @@ wheels = [ [[package]] name = "benchmarks" version = "0.1.0" -source = { editable = "benchmarks" } +source = { editable = "memora-dev/benchmarks" } dependencies = [ { name = "memora" }, { name = "openai" }, @@ -231,7 +230,7 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "memora", editable = "memora" }, + { name = "memora", directory = "memora" }, { name = "openai", specifier = ">=1.0.0" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "python-fasthtml", specifier = ">=0.12.33" }, @@ -408,7 +407,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.120.3" +version = "0.121.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -416,9 +415,9 @@ dependencies = [ { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/c6/f324c07f5ebe34237b56b6396a94568d2d4a705df8a2ff82fa45029e7252/fastapi-0.120.3.tar.gz", hash = "sha256:17db50718ee86c9e01e54f9d8600abf130f6f762711cd0d8f02eb392668271ba", size = 339363 } +sdist = { url = "https://files.pythonhosted.org/packages/6b/a4/29e1b861fc9017488ed02ff1052feffa40940cb355ed632a8845df84ce84/fastapi-0.121.1.tar.gz", hash = "sha256:b6dba0538fd15dab6fe4d3e5493c3957d8a9e1e9257f56446b5859af66f32441", size = 342523 } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/3a/1eef3ab55ede5af09186723898545a94d0a32b7ac9ea4e7af7bcb95f132a/fastapi-0.120.3-py3-none-any.whl", hash = "sha256:bfee21c98db9128dc425a686eafd14899e26e4471aab33076bff2427fd6dcd22", size = 108255 }, + { url = "https://files.pythonhosted.org/packages/94/fd/2e6f7d706899cc08690c5f6641e2ffbfffe019e8f16ce77104caa5730910/fastapi-0.121.1-py3-none-any.whl", hash = "sha256:2c5c7028bc3a58d8f5f09aecd3fd88a000ccc0c5ad627693264181a3c33aa1fc", size = 109192 }, ] [package.optional-dependencies] @@ -433,16 +432,16 @@ standard = [ [[package]] name = "fastapi-cli" -version = "0.0.14" +version = "0.0.16" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "rich-toolkit" }, { name = "typer" }, { name = "uvicorn", extra = ["standard"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/13/11e43d630be84e51ba5510a6da6a11eb93b44b72caa796137c5dddda937b/fastapi_cli-0.0.14.tar.gz", hash = "sha256:ddfb5de0a67f77a8b3271af1460489bd4d7f4add73d11fbfac613827b0275274", size = 17994 } +sdist = { url = "https://files.pythonhosted.org/packages/99/75/9407a6b452be4c988feacec9c9d2f58d8f315162a6c7258d5a649d933ebe/fastapi_cli-0.0.16.tar.gz", hash = "sha256:e8a2a1ecf7a4e062e3b2eec63ae34387d1e142d4849181d936b23c4bdfe29073", size = 19447 } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/e8/bc8bbfd93dcc8e347ce98a3e654fb0d2e5f2739afb46b98f41a30c339269/fastapi_cli-0.0.14-py3-none-any.whl", hash = "sha256:e66b9ad499ee77a4e6007545cde6de1459b7f21df199d7f29aad2adaab168eca", size = 11151 }, + { url = "https://files.pythonhosted.org/packages/55/43/678528c19318394320ee43757648d5e0a8070cf391b31f69d931e5c840d2/fastapi_cli-0.0.16-py3-none-any.whl", hash = "sha256:addcb6d130b5b9c91adbbf3f2947fe115991495fdb442fe3e51b5fc6327df9f4", size = 12312 }, ] [package.optional-dependencies] @@ -505,11 +504,11 @@ wheels = [ [[package]] name = "fsspec" -version = "2025.9.0" +version = "2025.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/e0/bab50af11c2d75c9c4a2a26a5254573c0bd97cea152254401510950486fa/fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19", size = 304847 } +sdist = { url = "https://files.pythonhosted.org/packages/24/7f/2747c0d332b9acfa75dc84447a066fdf812b5a6b8d30472b74d309bfe8cb/fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59", size = 309285 } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289 }, + { url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966 }, ] [[package]] @@ -748,87 +747,87 @@ wheels = [ [[package]] name = "jiter" -version = "0.11.1" +version = "0.12.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/68/0357982493a7b20925aece061f7fb7a2678e3b232f8d73a6edb7e5304443/jiter-0.11.1.tar.gz", hash = "sha256:849dcfc76481c0ea0099391235b7ca97d7279e0fa4c86005457ac7c88e8b76dc", size = 168385 } +sdist = { url = "https://files.pythonhosted.org/packages/45/9d/e0660989c1370e25848bb4c52d061c71837239738ad937e83edca174c273/jiter-0.12.0.tar.gz", hash = "sha256:64dfcd7d5c168b38d3f9f8bba7fc639edb3418abcc74f22fdbe6b8938293f30b", size = 168294 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/34/c9e6cfe876f9a24f43ed53fe29f052ce02bd8d5f5a387dbf46ad3764bef0/jiter-0.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9b0088ff3c374ce8ce0168523ec8e97122ebb788f950cf7bb8e39c7dc6a876a2", size = 310160 }, - { url = "https://files.pythonhosted.org/packages/bc/9f/b06ec8181d7165858faf2ac5287c54fe52b2287760b7fe1ba9c06890255f/jiter-0.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74433962dd3c3090655e02e461267095d6c84f0741c7827de11022ef8d7ff661", size = 316573 }, - { url = "https://files.pythonhosted.org/packages/66/49/3179d93090f2ed0c6b091a9c210f266d2d020d82c96f753260af536371d0/jiter-0.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d98030e345e6546df2cc2c08309c502466c66c4747b043f1a0d415fada862b8", size = 348998 }, - { url = "https://files.pythonhosted.org/packages/ae/9d/63db2c8eabda7a9cad65a2e808ca34aaa8689d98d498f5a2357d7a2e2cec/jiter-0.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d6db0b2e788db46bec2cf729a88b6dd36959af2abd9fa2312dfba5acdd96dcb", size = 363413 }, - { url = "https://files.pythonhosted.org/packages/25/ff/3e6b3170c5053053c7baddb8d44e2bf11ff44cd71024a280a8438ae6ba32/jiter-0.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55678fbbda261eafe7289165dd2ddd0e922df5f9a1ae46d7c79a5a15242bd7d1", size = 487144 }, - { url = "https://files.pythonhosted.org/packages/b0/50/b63fcadf699893269b997f4c2e88400bc68f085c6db698c6e5e69d63b2c1/jiter-0.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a6b74fae8e40497653b52ce6ca0f1b13457af769af6fb9c1113efc8b5b4d9be", size = 376215 }, - { url = "https://files.pythonhosted.org/packages/39/8c/57a8a89401134167e87e73471b9cca321cf651c1fd78c45f3a0f16932213/jiter-0.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a55a453f8b035eb4f7852a79a065d616b7971a17f5e37a9296b4b38d3b619e4", size = 359163 }, - { url = "https://files.pythonhosted.org/packages/4b/96/30b0cdbffbb6f753e25339d3dbbe26890c9ef119928314578201c758aace/jiter-0.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2638148099022e6bdb3f42904289cd2e403609356fb06eb36ddec2d50958bc29", size = 385344 }, - { url = "https://files.pythonhosted.org/packages/c6/d5/31dae27c1cc9410ad52bb514f11bfa4f286f7d6ef9d287b98b8831e156ec/jiter-0.11.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:252490567a5d990986f83b95a5f1ca1bf205ebd27b3e9e93bb7c2592380e29b9", size = 517972 }, - { url = "https://files.pythonhosted.org/packages/61/1e/5905a7a3aceab80de13ab226fd690471a5e1ee7e554dc1015e55f1a6b896/jiter-0.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d431d52b0ca2436eea6195f0f48528202100c7deda354cb7aac0a302167594d5", size = 508408 }, - { url = "https://files.pythonhosted.org/packages/91/12/1c49b97aa49077e136e8591cef7162f0d3e2860ae457a2d35868fd1521ef/jiter-0.11.1-cp311-cp311-win32.whl", hash = "sha256:db6f41e40f8bae20c86cb574b48c4fd9f28ee1c71cb044e9ec12e78ab757ba3a", size = 203937 }, - { url = "https://files.pythonhosted.org/packages/6d/9d/2255f7c17134ee9892c7e013c32d5bcf4bce64eb115402c9fe5e727a67eb/jiter-0.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0cc407b8e6cdff01b06bb80f61225c8b090c3df108ebade5e0c3c10993735b19", size = 207589 }, - { url = "https://files.pythonhosted.org/packages/3c/28/6307fc8f95afef84cae6caf5429fee58ef16a582c2ff4db317ceb3e352fa/jiter-0.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:fe04ea475392a91896d1936367854d346724a1045a247e5d1c196410473b8869", size = 188391 }, - { url = "https://files.pythonhosted.org/packages/15/8b/318e8af2c904a9d29af91f78c1e18f0592e189bbdb8a462902d31fe20682/jiter-0.11.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c92148eec91052538ce6823dfca9525f5cfc8b622d7f07e9891a280f61b8c96c", size = 305655 }, - { url = "https://files.pythonhosted.org/packages/f7/29/6c7de6b5d6e511d9e736312c0c9bfcee8f9b6bef68182a08b1d78767e627/jiter-0.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd4da91b5415f183a6be8f7158d127bdd9e6a3174138293c0d48d6ea2f2009d", size = 315645 }, - { url = "https://files.pythonhosted.org/packages/ac/5f/ef9e5675511ee0eb7f98dd8c90509e1f7743dbb7c350071acae87b0145f3/jiter-0.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e3ac25c00b9275684d47aa42febaa90a9958e19fd1726c4ecf755fbe5e553b", size = 348003 }, - { url = "https://files.pythonhosted.org/packages/56/1b/abe8c4021010b0a320d3c62682769b700fb66f92c6db02d1a1381b3db025/jiter-0.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57d7305c0a841858f866cd459cd9303f73883fb5e097257f3d4a3920722c69d4", size = 365122 }, - { url = "https://files.pythonhosted.org/packages/2a/2d/4a18013939a4f24432f805fbd5a19893e64650b933edb057cd405275a538/jiter-0.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e86fa10e117dce22c547f31dd6d2a9a222707d54853d8de4e9a2279d2c97f239", size = 488360 }, - { url = "https://files.pythonhosted.org/packages/f0/77/38124f5d02ac4131f0dfbcfd1a19a0fac305fa2c005bc4f9f0736914a1a4/jiter-0.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae5ef1d48aec7e01ee8420155d901bb1d192998fa811a65ebb82c043ee186711", size = 376884 }, - { url = "https://files.pythonhosted.org/packages/7b/43/59fdc2f6267959b71dd23ce0bd8d4aeaf55566aa435a5d00f53d53c7eb24/jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb68e7bf65c990531ad8715e57d50195daf7c8e6f1509e617b4e692af1108939", size = 358827 }, - { url = "https://files.pythonhosted.org/packages/7d/d0/b3cc20ff5340775ea3bbaa0d665518eddecd4266ba7244c9cb480c0c82ec/jiter-0.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43b30c8154ded5845fa454ef954ee67bfccce629b2dea7d01f795b42bc2bda54", size = 385171 }, - { url = "https://files.pythonhosted.org/packages/d2/bc/94dd1f3a61f4dc236f787a097360ec061ceeebebf4ea120b924d91391b10/jiter-0.11.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:586cafbd9dd1f3ce6a22b4a085eaa6be578e47ba9b18e198d4333e598a91db2d", size = 518359 }, - { url = "https://files.pythonhosted.org/packages/7e/8c/12ee132bd67e25c75f542c227f5762491b9a316b0dad8e929c95076f773c/jiter-0.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:677cc2517d437a83bb30019fd4cf7cad74b465914c56ecac3440d597ac135250", size = 509205 }, - { url = "https://files.pythonhosted.org/packages/39/d5/9de848928ce341d463c7e7273fce90ea6d0ea4343cd761f451860fa16b59/jiter-0.11.1-cp312-cp312-win32.whl", hash = "sha256:fa992af648fcee2b850a3286a35f62bbbaeddbb6dbda19a00d8fbc846a947b6e", size = 205448 }, - { url = "https://files.pythonhosted.org/packages/ee/b0/8002d78637e05009f5e3fb5288f9d57d65715c33b5d6aa20fd57670feef5/jiter-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88b5cae9fa51efeb3d4bd4e52bfd4c85ccc9cac44282e2a9640893a042ba4d87", size = 204285 }, - { url = "https://files.pythonhosted.org/packages/9f/a2/bb24d5587e4dff17ff796716542f663deee337358006a80c8af43ddc11e5/jiter-0.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:9a6cae1ab335551917f882f2c3c1efe7617b71b4c02381e4382a8fc80a02588c", size = 188712 }, - { url = "https://files.pythonhosted.org/packages/7c/4b/e4dd3c76424fad02a601d570f4f2a8438daea47ba081201a721a903d3f4c/jiter-0.11.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:71b6a920a5550f057d49d0e8bcc60945a8da998019e83f01adf110e226267663", size = 305272 }, - { url = "https://files.pythonhosted.org/packages/67/83/2cd3ad5364191130f4de80eacc907f693723beaab11a46c7d155b07a092c/jiter-0.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b3de72e925388453a5171be83379549300db01284f04d2a6f244d1d8de36f94", size = 314038 }, - { url = "https://files.pythonhosted.org/packages/d3/3c/8e67d9ba524e97d2f04c8f406f8769a23205026b13b0938d16646d6e2d3e/jiter-0.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc19dd65a2bd3d9c044c5b4ebf657ca1e6003a97c0fc10f555aa4f7fb9821c00", size = 345977 }, - { url = "https://files.pythonhosted.org/packages/8d/a5/489ce64d992c29bccbffabb13961bbb0435e890d7f2d266d1f3df5e917d2/jiter-0.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d58faaa936743cd1464540562f60b7ce4fd927e695e8bc31b3da5b914baa9abd", size = 364503 }, - { url = "https://files.pythonhosted.org/packages/d4/c0/e321dd83ee231d05c8fe4b1a12caf1f0e8c7a949bf4724d58397104f10f2/jiter-0.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:902640c3103625317291cb73773413b4d71847cdf9383ba65528745ff89f1d14", size = 487092 }, - { url = "https://files.pythonhosted.org/packages/f9/5e/8f24ec49c8d37bd37f34ec0112e0b1a3b4b5a7b456c8efff1df5e189ad43/jiter-0.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30405f726e4c2ed487b176c09f8b877a957f535d60c1bf194abb8dadedb5836f", size = 376328 }, - { url = "https://files.pythonhosted.org/packages/7f/70/ded107620e809327cf7050727e17ccfa79d6385a771b7fe38fb31318ef00/jiter-0.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3217f61728b0baadd2551844870f65219ac4a1285d5e1a4abddff3d51fdabe96", size = 356632 }, - { url = "https://files.pythonhosted.org/packages/19/53/c26f7251613f6a9079275ee43c89b8a973a95ff27532c421abc2a87afb04/jiter-0.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1364cc90c03a8196f35f396f84029f12abe925415049204446db86598c8b72c", size = 384358 }, - { url = "https://files.pythonhosted.org/packages/84/16/e0f2cc61e9c4d0b62f6c1bd9b9781d878a427656f88293e2a5335fa8ff07/jiter-0.11.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:53a54bf8e873820ab186b2dca9f6c3303f00d65ae5e7b7d6bda1b95aa472d646", size = 517279 }, - { url = "https://files.pythonhosted.org/packages/60/5c/4cd095eaee68961bca3081acbe7c89e12ae24a5dae5fd5d2a13e01ed2542/jiter-0.11.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7e29aca023627b0e0c2392d4248f6414d566ff3974fa08ff2ac8dbb96dfee92a", size = 508276 }, - { url = "https://files.pythonhosted.org/packages/4f/25/f459240e69b0e09a7706d96ce203ad615ca36b0fe832308d2b7123abf2d0/jiter-0.11.1-cp313-cp313-win32.whl", hash = "sha256:f153e31d8bca11363751e875c0a70b3d25160ecbaee7b51e457f14498fb39d8b", size = 205593 }, - { url = "https://files.pythonhosted.org/packages/7c/16/461bafe22bae79bab74e217a09c907481a46d520c36b7b9fe71ee8c9e983/jiter-0.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:f773f84080b667c69c4ea0403fc67bb08b07e2b7ce1ef335dea5868451e60fed", size = 203518 }, - { url = "https://files.pythonhosted.org/packages/7b/72/c45de6e320edb4fa165b7b1a414193b3cae302dd82da2169d315dcc78b44/jiter-0.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:635ecd45c04e4c340d2187bcb1cea204c7cc9d32c1364d251564bf42e0e39c2d", size = 188062 }, - { url = "https://files.pythonhosted.org/packages/65/9b/4a57922437ca8753ef823f434c2dec5028b237d84fa320f06a3ba1aec6e8/jiter-0.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d892b184da4d94d94ddb4031296931c74ec8b325513a541ebfd6dfb9ae89904b", size = 313814 }, - { url = "https://files.pythonhosted.org/packages/76/50/62a0683dadca25490a4bedc6a88d59de9af2a3406dd5a576009a73a1d392/jiter-0.11.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa22c223a3041dacb2fcd37c70dfd648b44662b4a48e242592f95bda5ab09d58", size = 344987 }, - { url = "https://files.pythonhosted.org/packages/da/00/2355dbfcbf6cdeaddfdca18287f0f38ae49446bb6378e4a5971e9356fc8a/jiter-0.11.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330e8e6a11ad4980cd66a0f4a3e0e2e0f646c911ce047014f984841924729789", size = 356399 }, - { url = "https://files.pythonhosted.org/packages/c9/07/c2bd748d578fa933d894a55bff33f983bc27f75fc4e491b354bef7b78012/jiter-0.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:09e2e386ebf298547ca3a3704b729471f7ec666c2906c5c26c1a915ea24741ec", size = 203289 }, - { url = "https://files.pythonhosted.org/packages/e6/ee/ace64a853a1acbd318eb0ca167bad1cf5ee037207504b83a868a5849747b/jiter-0.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:fe4a431c291157e11cee7c34627990ea75e8d153894365a3bc84b7a959d23ca8", size = 188284 }, - { url = "https://files.pythonhosted.org/packages/8d/00/d6006d069e7b076e4c66af90656b63da9481954f290d5eca8c715f4bf125/jiter-0.11.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:0fa1f70da7a8a9713ff8e5f75ec3f90c0c870be6d526aa95e7c906f6a1c8c676", size = 304624 }, - { url = "https://files.pythonhosted.org/packages/fc/45/4a0e31eb996b9ccfddbae4d3017b46f358a599ccf2e19fbffa5e531bd304/jiter-0.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:569ee559e5046a42feb6828c55307cf20fe43308e3ae0d8e9e4f8d8634d99944", size = 315042 }, - { url = "https://files.pythonhosted.org/packages/e7/91/22f5746f5159a28c76acdc0778801f3c1181799aab196dbea2d29e064968/jiter-0.11.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f69955fa1d92e81987f092b233f0be49d4c937da107b7f7dcf56306f1d3fcce9", size = 346357 }, - { url = "https://files.pythonhosted.org/packages/f5/4f/57620857d4e1dc75c8ff4856c90cb6c135e61bff9b4ebfb5dc86814e82d7/jiter-0.11.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:090f4c9d4a825e0fcbd0a2647c9a88a0f366b75654d982d95a9590745ff0c48d", size = 365057 }, - { url = "https://files.pythonhosted.org/packages/ce/34/caf7f9cc8ae0a5bb25a5440cc76c7452d264d1b36701b90fdadd28fe08ec/jiter-0.11.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf3d8cedf9e9d825233e0dcac28ff15c47b7c5512fdfe2e25fd5bbb6e6b0cee", size = 487086 }, - { url = "https://files.pythonhosted.org/packages/50/17/85b5857c329d533d433fedf98804ebec696004a1f88cabad202b2ddc55cf/jiter-0.11.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa9b1958f9c30d3d1a558b75f0626733c60eb9b7774a86b34d88060be1e67fe", size = 376083 }, - { url = "https://files.pythonhosted.org/packages/85/d3/2d9f973f828226e6faebdef034097a2918077ea776fb4d88489949024787/jiter-0.11.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42d1ca16590b768c5e7d723055acd2633908baacb3628dd430842e2e035aa90", size = 357825 }, - { url = "https://files.pythonhosted.org/packages/f4/55/848d4dabf2c2c236a05468c315c2cb9dc736c5915e65449ccecdba22fb6f/jiter-0.11.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5db4c2486a023820b701a17aec9c5a6173c5ba4393f26662f032f2de9c848b0f", size = 383933 }, - { url = "https://files.pythonhosted.org/packages/0b/6c/204c95a4fbb0e26dfa7776c8ef4a878d0c0b215868011cc904bf44f707e2/jiter-0.11.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:4573b78777ccfac954859a6eff45cbd9d281d80c8af049d0f1a3d9fc323d5c3a", size = 517118 }, - { url = "https://files.pythonhosted.org/packages/88/25/09956644ea5a2b1e7a2a0f665cb69a973b28f4621fa61fc0c0f06ff40a31/jiter-0.11.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7593ac6f40831d7961cb67633c39b9fef6689a211d7919e958f45710504f52d3", size = 508194 }, - { url = "https://files.pythonhosted.org/packages/09/49/4d1657355d7f5c9e783083a03a3f07d5858efa6916a7d9634d07db1c23bd/jiter-0.11.1-cp314-cp314-win32.whl", hash = "sha256:87202ec6ff9626ff5f9351507def98fcf0df60e9a146308e8ab221432228f4ea", size = 203961 }, - { url = "https://files.pythonhosted.org/packages/76/bd/f063bd5cc2712e7ca3cf6beda50894418fc0cfeb3f6ff45a12d87af25996/jiter-0.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:a5dd268f6531a182c89d0dd9a3f8848e86e92dfff4201b77a18e6b98aa59798c", size = 202804 }, - { url = "https://files.pythonhosted.org/packages/52/ca/4d84193dfafef1020bf0bedd5e1a8d0e89cb67c54b8519040effc694964b/jiter-0.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:5d761f863f912a44748a21b5c4979c04252588ded8d1d2760976d2e42cd8d991", size = 188001 }, - { url = "https://files.pythonhosted.org/packages/d5/fa/3b05e5c9d32efc770a8510eeb0b071c42ae93a5b576fd91cee9af91689a1/jiter-0.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2cc5a3965285ddc33e0cab933e96b640bc9ba5940cea27ebbbf6695e72d6511c", size = 312561 }, - { url = "https://files.pythonhosted.org/packages/50/d3/335822eb216154ddb79a130cbdce88fdf5c3e2b43dc5dba1fd95c485aaf5/jiter-0.11.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b572b3636a784c2768b2342f36a23078c8d3aa6d8a30745398b1bab58a6f1a8", size = 344551 }, - { url = "https://files.pythonhosted.org/packages/31/6d/a0bed13676b1398f9b3ba61f32569f20a3ff270291161100956a577b2dd3/jiter-0.11.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad93e3d67a981f96596d65d2298fe8d1aa649deb5374a2fb6a434410ee11915e", size = 363051 }, - { url = "https://files.pythonhosted.org/packages/a4/03/313eda04aa08545a5a04ed5876e52f49ab76a4d98e54578896ca3e16313e/jiter-0.11.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83097ce379e202dcc3fe3fc71a16d523d1ee9192c8e4e854158f96b3efe3f2f", size = 485897 }, - { url = "https://files.pythonhosted.org/packages/5f/13/a1011b9d325e40b53b1b96a17c010b8646013417f3902f97a86325b19299/jiter-0.11.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7042c51e7fbeca65631eb0c332f90c0c082eab04334e7ccc28a8588e8e2804d9", size = 375224 }, - { url = "https://files.pythonhosted.org/packages/92/da/1b45026b19dd39b419e917165ff0ea629dbb95f374a3a13d2df95e40a6ac/jiter-0.11.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a68d679c0e47649a61df591660507608adc2652442de7ec8276538ac46abe08", size = 356606 }, - { url = "https://files.pythonhosted.org/packages/7a/0c/9acb0e54d6a8ba59ce923a180ebe824b4e00e80e56cefde86cc8e0a948be/jiter-0.11.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1b0da75dbf4b6ec0b3c9e604d1ee8beaf15bc046fff7180f7d89e3cdbd3bb51", size = 384003 }, - { url = "https://files.pythonhosted.org/packages/3f/2b/e5a5fe09d6da2145e4eed651e2ce37f3c0cf8016e48b1d302e21fb1628b7/jiter-0.11.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:69dd514bf0fa31c62147d6002e5ca2b3e7ef5894f5ac6f0a19752385f4e89437", size = 516946 }, - { url = "https://files.pythonhosted.org/packages/5f/fe/db936e16e0228d48eb81f9934e8327e9fde5185e84f02174fcd22a01be87/jiter-0.11.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:bb31ac0b339efa24c0ca606febd8b77ef11c58d09af1b5f2be4c99e907b11111", size = 507614 }, - { url = "https://files.pythonhosted.org/packages/86/db/c4438e8febfb303486d13c6b72f5eb71cf851e300a0c1f0b4140018dd31f/jiter-0.11.1-cp314-cp314t-win32.whl", hash = "sha256:b2ce0d6156a1d3ad41da3eec63b17e03e296b78b0e0da660876fccfada86d2f7", size = 204043 }, - { url = "https://files.pythonhosted.org/packages/36/59/81badb169212f30f47f817dfaabf965bc9b8204fed906fab58104ee541f9/jiter-0.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f4db07d127b54c4a2d43b4cf05ff0193e4f73e0dd90c74037e16df0b29f666e1", size = 204046 }, - { url = "https://files.pythonhosted.org/packages/dd/01/43f7b4eb61db3e565574c4c5714685d042fb652f9eef7e5a3de6aafa943a/jiter-0.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:28e4fdf2d7ebfc935523e50d1efa3970043cfaa161674fe66f9642409d001dfe", size = 188069 }, - { url = "https://files.pythonhosted.org/packages/9d/51/bd41562dd284e2a18b6dc0a99d195fd4a3560d52ab192c42e56fe0316643/jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:e642b5270e61dd02265866398707f90e365b5db2eb65a4f30c789d826682e1f6", size = 306871 }, - { url = "https://files.pythonhosted.org/packages/ba/cb/64e7f21dd357e8cd6b3c919c26fac7fc198385bbd1d85bb3b5355600d787/jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:464ba6d000585e4e2fd1e891f31f1231f497273414f5019e27c00a4b8f7a24ad", size = 301454 }, - { url = "https://files.pythonhosted.org/packages/55/b0/54bdc00da4ef39801b1419a01035bd8857983de984fd3776b0be6b94add7/jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:055568693ab35e0bf3a171b03bb40b2dcb10352359e0ab9b5ed0da2bf1eb6f6f", size = 336801 }, - { url = "https://files.pythonhosted.org/packages/de/8f/87176ed071d42e9db415ed8be787ef4ef31a4fa27f52e6a4fbf34387bd28/jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c69ea798d08a915ba4478113efa9e694971e410056392f4526d796f136d3fa", size = 343452 }, - { url = "https://files.pythonhosted.org/packages/a6/bc/950dd7f170c6394b6fdd73f989d9e729bd98907bcc4430ef080a72d06b77/jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:0d4d6993edc83cf75e8c6828a8d6ce40a09ee87e38c7bfba6924f39e1337e21d", size = 302626 }, - { url = "https://files.pythonhosted.org/packages/3a/65/43d7971ca82ee100b7b9b520573eeef7eabc0a45d490168ebb9a9b5bb8b2/jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f78d151c83a87a6cf5461d5ee55bc730dd9ae227377ac6f115b922989b95f838", size = 297034 }, - { url = "https://files.pythonhosted.org/packages/19/4c/000e1e0c0c67e96557a279f8969487ea2732d6c7311698819f977abae837/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9022974781155cd5521d5cb10997a03ee5e31e8454c9d999dcdccd253f2353f", size = 337328 }, - { url = "https://files.pythonhosted.org/packages/d9/71/71408b02c6133153336d29fa3ba53000f1e1a3f78bb2fc2d1a1865d2e743/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18c77aaa9117510d5bdc6a946baf21b1f0cfa58ef04d31c8d016f206f2118960", size = 343697 }, + { url = "https://files.pythonhosted.org/packages/32/f9/eaca4633486b527ebe7e681c431f529b63fe2709e7c5242fc0f43f77ce63/jiter-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8f8a7e317190b2c2d60eb2e8aa835270b008139562d70fe732e1c0020ec53c9", size = 316435 }, + { url = "https://files.pythonhosted.org/packages/10/c1/40c9f7c22f5e6ff715f28113ebaba27ab85f9af2660ad6e1dd6425d14c19/jiter-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2218228a077e784c6c8f1a8e5d6b8cb1dea62ce25811c356364848554b2056cd", size = 320548 }, + { url = "https://files.pythonhosted.org/packages/6b/1b/efbb68fe87e7711b00d2cfd1f26bb4bfc25a10539aefeaa7727329ffb9cb/jiter-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9354ccaa2982bf2188fd5f57f79f800ef622ec67beb8329903abf6b10da7d423", size = 351915 }, + { url = "https://files.pythonhosted.org/packages/15/2d/c06e659888c128ad1e838123d0638f0efad90cc30860cb5f74dd3f2fc0b3/jiter-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2607185ea89b4af9a604d4c7ec40e45d3ad03ee66998b031134bc510232bb7", size = 368966 }, + { url = "https://files.pythonhosted.org/packages/6b/20/058db4ae5fb07cf6a4ab2e9b9294416f606d8e467fb74c2184b2a1eeacba/jiter-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a585a5e42d25f2e71db5f10b171f5e5ea641d3aa44f7df745aa965606111cc2", size = 482047 }, + { url = "https://files.pythonhosted.org/packages/49/bb/dc2b1c122275e1de2eb12905015d61e8316b2f888bdaac34221c301495d6/jiter-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd9e21d34edff5a663c631f850edcb786719c960ce887a5661e9c828a53a95d9", size = 380835 }, + { url = "https://files.pythonhosted.org/packages/23/7d/38f9cd337575349de16da575ee57ddb2d5a64d425c9367f5ef9e4612e32e/jiter-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a612534770470686cd5431478dc5a1b660eceb410abade6b1b74e320ca98de6", size = 364587 }, + { url = "https://files.pythonhosted.org/packages/f0/a3/b13e8e61e70f0bb06085099c4e2462647f53cc2ca97614f7fedcaa2bb9f3/jiter-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3985aea37d40a908f887b34d05111e0aae822943796ebf8338877fee2ab67725", size = 390492 }, + { url = "https://files.pythonhosted.org/packages/07/71/e0d11422ed027e21422f7bc1883c61deba2d9752b720538430c1deadfbca/jiter-0.12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b1207af186495f48f72529f8d86671903c8c10127cac6381b11dddc4aaa52df6", size = 522046 }, + { url = "https://files.pythonhosted.org/packages/9f/59/b968a9aa7102a8375dbbdfbd2aeebe563c7e5dddf0f47c9ef1588a97e224/jiter-0.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef2fb241de583934c9915a33120ecc06d94aa3381a134570f59eed784e87001e", size = 513392 }, + { url = "https://files.pythonhosted.org/packages/ca/e4/7df62002499080dbd61b505c5cb351aa09e9959d176cac2aa8da6f93b13b/jiter-0.12.0-cp311-cp311-win32.whl", hash = "sha256:453b6035672fecce8007465896a25b28a6b59cfe8fbc974b2563a92f5a92a67c", size = 206096 }, + { url = "https://files.pythonhosted.org/packages/bb/60/1032b30ae0572196b0de0e87dce3b6c26a1eff71aad5fe43dee3082d32e0/jiter-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca264b9603973c2ad9435c71a8ec8b49f8f715ab5ba421c85a51cde9887e421f", size = 204899 }, + { url = "https://files.pythonhosted.org/packages/49/d5/c145e526fccdb834063fb45c071df78b0cc426bbaf6de38b0781f45d956f/jiter-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:cb00ef392e7d684f2754598c02c409f376ddcef857aae796d559e6cacc2d78a5", size = 188070 }, + { url = "https://files.pythonhosted.org/packages/92/c9/5b9f7b4983f1b542c64e84165075335e8a236fa9e2ea03a0c79780062be8/jiter-0.12.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:305e061fa82f4680607a775b2e8e0bcb071cd2205ac38e6ef48c8dd5ebe1cf37", size = 314449 }, + { url = "https://files.pythonhosted.org/packages/98/6e/e8efa0e78de00db0aee82c0cf9e8b3f2027efd7f8a71f859d8f4be8e98ef/jiter-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1860627048e302a528333c9307c818c547f214d8659b0705d2195e1a94b274", size = 319855 }, + { url = "https://files.pythonhosted.org/packages/20/26/894cd88e60b5d58af53bec5c6759d1292bd0b37a8b5f60f07abf7a63ae5f/jiter-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df37577a4f8408f7e0ec3205d2a8f87672af8f17008358063a4d6425b6081ce3", size = 350171 }, + { url = "https://files.pythonhosted.org/packages/f5/27/a7b818b9979ac31b3763d25f3653ec3a954044d5e9f5d87f2f247d679fd1/jiter-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fdd787356c1c13a4f40b43c2156276ef7a71eb487d98472476476d803fb2cf", size = 365590 }, + { url = "https://files.pythonhosted.org/packages/ba/7e/e46195801a97673a83746170b17984aa8ac4a455746354516d02ca5541b4/jiter-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1eb5db8d9c65b112aacf14fcd0faae9913d07a8afea5ed06ccdd12b724e966a1", size = 479462 }, + { url = "https://files.pythonhosted.org/packages/ca/75/f833bfb009ab4bd11b1c9406d333e3b4357709ed0570bb48c7c06d78c7dd/jiter-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73c568cc27c473f82480abc15d1301adf333a7ea4f2e813d6a2c7d8b6ba8d0df", size = 378983 }, + { url = "https://files.pythonhosted.org/packages/71/b3/7a69d77943cc837d30165643db753471aff5df39692d598da880a6e51c24/jiter-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4321e8a3d868919bcb1abb1db550d41f2b5b326f72df29e53b2df8b006eb9403", size = 361328 }, + { url = "https://files.pythonhosted.org/packages/b0/ac/a78f90caf48d65ba70d8c6efc6f23150bc39dc3389d65bbec2a95c7bc628/jiter-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a51bad79f8cc9cac2b4b705039f814049142e0050f30d91695a2d9a6611f126", size = 386740 }, + { url = "https://files.pythonhosted.org/packages/39/b6/5d31c2cc8e1b6a6bcf3c5721e4ca0a3633d1ab4754b09bc7084f6c4f5327/jiter-0.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a67b678f6a5f1dd6c36d642d7db83e456bc8b104788262aaefc11a22339f5a9", size = 520875 }, + { url = "https://files.pythonhosted.org/packages/30/b5/4df540fae4e9f68c54b8dab004bd8c943a752f0b00efd6e7d64aa3850339/jiter-0.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efe1a211fe1fd14762adea941e3cfd6c611a136e28da6c39272dbb7a1bbe6a86", size = 511457 }, + { url = "https://files.pythonhosted.org/packages/07/65/86b74010e450a1a77b2c1aabb91d4a91dd3cd5afce99f34d75fd1ac64b19/jiter-0.12.0-cp312-cp312-win32.whl", hash = "sha256:d779d97c834b4278276ec703dc3fc1735fca50af63eb7262f05bdb4e62203d44", size = 204546 }, + { url = "https://files.pythonhosted.org/packages/1c/c7/6659f537f9562d963488e3e55573498a442503ced01f7e169e96a6110383/jiter-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8269062060212b373316fe69236096aaf4c49022d267c6736eebd66bbbc60bb", size = 205196 }, + { url = "https://files.pythonhosted.org/packages/21/f4/935304f5169edadfec7f9c01eacbce4c90bb9a82035ac1de1f3bd2d40be6/jiter-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:06cb970936c65de926d648af0ed3d21857f026b1cf5525cb2947aa5e01e05789", size = 186100 }, + { url = "https://files.pythonhosted.org/packages/3d/a6/97209693b177716e22576ee1161674d1d58029eb178e01866a0422b69224/jiter-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6cc49d5130a14b732e0612bc76ae8db3b49898732223ef8b7599aa8d9810683e", size = 313658 }, + { url = "https://files.pythonhosted.org/packages/06/4d/125c5c1537c7d8ee73ad3d530a442d6c619714b95027143f1b61c0b4dfe0/jiter-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37f27a32ce36364d2fa4f7fdc507279db604d27d239ea2e044c8f148410defe1", size = 318605 }, + { url = "https://files.pythonhosted.org/packages/99/bf/a840b89847885064c41a5f52de6e312e91fa84a520848ee56c97e4fa0205/jiter-0.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc0944aa3d4b4773e348cda635252824a78f4ba44328e042ef1ff3f6080d1cf", size = 349803 }, + { url = "https://files.pythonhosted.org/packages/8a/88/e63441c28e0db50e305ae23e19c1d8fae012d78ed55365da392c1f34b09c/jiter-0.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da25c62d4ee1ffbacb97fac6dfe4dcd6759ebdc9015991e92a6eae5816287f44", size = 365120 }, + { url = "https://files.pythonhosted.org/packages/0a/7c/49b02714af4343970eb8aca63396bc1c82fa01197dbb1e9b0d274b550d4e/jiter-0.12.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:048485c654b838140b007390b8182ba9774621103bd4d77c9c3f6f117474ba45", size = 479918 }, + { url = "https://files.pythonhosted.org/packages/69/ba/0a809817fdd5a1db80490b9150645f3aae16afad166960bcd562be194f3b/jiter-0.12.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:635e737fbb7315bef0037c19b88b799143d2d7d3507e61a76751025226b3ac87", size = 379008 }, + { url = "https://files.pythonhosted.org/packages/5f/c3/c9fc0232e736c8877d9e6d83d6eeb0ba4e90c6c073835cc2e8f73fdeef51/jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e017c417b1ebda911bd13b1e40612704b1f5420e30695112efdbed8a4b389ed", size = 361785 }, + { url = "https://files.pythonhosted.org/packages/96/61/61f69b7e442e97ca6cd53086ddc1cf59fb830549bc72c0a293713a60c525/jiter-0.12.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:89b0bfb8b2bf2351fba36bb211ef8bfceba73ef58e7f0c68fb67b5a2795ca2f9", size = 386108 }, + { url = "https://files.pythonhosted.org/packages/e9/2e/76bb3332f28550c8f1eba3bf6e5efe211efda0ddbbaf24976bc7078d42a5/jiter-0.12.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f5aa5427a629a824a543672778c9ce0c5e556550d1569bb6ea28a85015287626", size = 519937 }, + { url = "https://files.pythonhosted.org/packages/84/d6/fa96efa87dc8bff2094fb947f51f66368fa56d8d4fc9e77b25d7fbb23375/jiter-0.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed53b3d6acbcb0fd0b90f20c7cb3b24c357fe82a3518934d4edfa8c6898e498c", size = 510853 }, + { url = "https://files.pythonhosted.org/packages/8a/28/93f67fdb4d5904a708119a6ab58a8f1ec226ff10a94a282e0215402a8462/jiter-0.12.0-cp313-cp313-win32.whl", hash = "sha256:4747de73d6b8c78f2e253a2787930f4fffc68da7fa319739f57437f95963c4de", size = 204699 }, + { url = "https://files.pythonhosted.org/packages/c4/1f/30b0eb087045a0abe2a5c9c0c0c8da110875a1d3be83afd4a9a4e548be3c/jiter-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:e25012eb0c456fcc13354255d0338cd5397cce26c77b2832b3c4e2e255ea5d9a", size = 204258 }, + { url = "https://files.pythonhosted.org/packages/2c/f4/2b4daf99b96bce6fc47971890b14b2a36aef88d7beb9f057fafa032c6141/jiter-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:c97b92c54fe6110138c872add030a1f99aea2401ddcdaa21edf74705a646dd60", size = 185503 }, + { url = "https://files.pythonhosted.org/packages/39/ca/67bb15a7061d6fe20b9b2a2fd783e296a1e0f93468252c093481a2f00efa/jiter-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:53839b35a38f56b8be26a7851a48b89bc47e5d88e900929df10ed93b95fea3d6", size = 317965 }, + { url = "https://files.pythonhosted.org/packages/18/af/1788031cd22e29c3b14bc6ca80b16a39a0b10e611367ffd480c06a259831/jiter-0.12.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f669548e55c91ab47fef8bddd9c954dab1938644e715ea49d7e117015110a4", size = 345831 }, + { url = "https://files.pythonhosted.org/packages/05/17/710bf8472d1dff0d3caf4ced6031060091c1320f84ee7d5dcbed1f352417/jiter-0.12.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351d54f2b09a41600ffea43d081522d792e81dcfb915f6d2d242744c1cc48beb", size = 361272 }, + { url = "https://files.pythonhosted.org/packages/fb/f1/1dcc4618b59761fef92d10bcbb0b038b5160be653b003651566a185f1a5c/jiter-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2a5e90604620f94bf62264e7c2c038704d38217b7465b863896c6d7c902b06c7", size = 204604 }, + { url = "https://files.pythonhosted.org/packages/d9/32/63cb1d9f1c5c6632a783c0052cde9ef7ba82688f7065e2f0d5f10a7e3edb/jiter-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:88ef757017e78d2860f96250f9393b7b577b06a956ad102c29c8237554380db3", size = 185628 }, + { url = "https://files.pythonhosted.org/packages/a8/99/45c9f0dbe4a1416b2b9a8a6d1236459540f43d7fb8883cff769a8db0612d/jiter-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c46d927acd09c67a9fb1416df45c5a04c27e83aae969267e98fba35b74e99525", size = 312478 }, + { url = "https://files.pythonhosted.org/packages/4c/a7/54ae75613ba9e0f55fcb0bc5d1f807823b5167cc944e9333ff322e9f07dd/jiter-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:774ff60b27a84a85b27b88cd5583899c59940bcc126caca97eb2a9df6aa00c49", size = 318706 }, + { url = "https://files.pythonhosted.org/packages/59/31/2aa241ad2c10774baf6c37f8b8e1f39c07db358f1329f4eb40eba179c2a2/jiter-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5433fab222fb072237df3f637d01b81f040a07dcac1cb4a5c75c7aa9ed0bef1", size = 351894 }, + { url = "https://files.pythonhosted.org/packages/54/4f/0f2759522719133a9042781b18cc94e335b6d290f5e2d3e6899d6af933e3/jiter-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8c593c6e71c07866ec6bfb790e202a833eeec885022296aff6b9e0b92d6a70e", size = 365714 }, + { url = "https://files.pythonhosted.org/packages/dc/6f/806b895f476582c62a2f52c453151edd8a0fde5411b0497baaa41018e878/jiter-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90d32894d4c6877a87ae00c6b915b609406819dce8bc0d4e962e4de2784e567e", size = 478989 }, + { url = "https://files.pythonhosted.org/packages/86/6c/012d894dc6e1033acd8db2b8346add33e413ec1c7c002598915278a37f79/jiter-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:798e46eed9eb10c3adbbacbd3bdb5ecd4cf7064e453d00dbef08802dae6937ff", size = 378615 }, + { url = "https://files.pythonhosted.org/packages/87/30/d718d599f6700163e28e2c71c0bbaf6dace692e7df2592fd793ac9276717/jiter-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3f1368f0a6719ea80013a4eb90ba72e75d7ea67cfc7846db2ca504f3df0169a", size = 364745 }, + { url = "https://files.pythonhosted.org/packages/8f/85/315b45ce4b6ddc7d7fceca24068543b02bdc8782942f4ee49d652e2cc89f/jiter-0.12.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f04a9d0b4406f7e51279710b27484af411896246200e461d80d3ba0caa901a", size = 386502 }, + { url = "https://files.pythonhosted.org/packages/74/0b/ce0434fb40c5b24b368fe81b17074d2840748b4952256bab451b72290a49/jiter-0.12.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fd990541982a24281d12b67a335e44f117e4c6cbad3c3b75c7dea68bf4ce3a67", size = 519845 }, + { url = "https://files.pythonhosted.org/packages/e8/a3/7a7a4488ba052767846b9c916d208b3ed114e3eb670ee984e4c565b9cf0d/jiter-0.12.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b111b0e9152fa7df870ecaebb0bd30240d9f7fff1f2003bcb4ed0f519941820b", size = 510701 }, + { url = "https://files.pythonhosted.org/packages/c3/16/052ffbf9d0467b70af24e30f91e0579e13ded0c17bb4a8eb2aed3cb60131/jiter-0.12.0-cp314-cp314-win32.whl", hash = "sha256:a78befb9cc0a45b5a5a0d537b06f8544c2ebb60d19d02c41ff15da28a9e22d42", size = 205029 }, + { url = "https://files.pythonhosted.org/packages/e4/18/3cf1f3f0ccc789f76b9a754bdb7a6977e5d1d671ee97a9e14f7eb728d80e/jiter-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:e1fe01c082f6aafbe5c8faf0ff074f38dfb911d53f07ec333ca03f8f6226debf", size = 204960 }, + { url = "https://files.pythonhosted.org/packages/02/68/736821e52ecfdeeb0f024b8ab01b5a229f6b9293bbdb444c27efade50b0f/jiter-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:d72f3b5a432a4c546ea4bedc84cce0c3404874f1d1676260b9c7f048a9855451", size = 185529 }, + { url = "https://files.pythonhosted.org/packages/30/61/12ed8ee7a643cce29ac97c2281f9ce3956eb76b037e88d290f4ed0d41480/jiter-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6ded41aeba3603f9728ed2b6196e4df875348ab97b28fc8afff115ed42ba7a7", size = 318974 }, + { url = "https://files.pythonhosted.org/packages/2d/c6/f3041ede6d0ed5e0e79ff0de4c8f14f401bbf196f2ef3971cdbe5fd08d1d/jiter-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a947920902420a6ada6ad51892082521978e9dd44a802663b001436e4b771684", size = 345932 }, + { url = "https://files.pythonhosted.org/packages/d5/5d/4d94835889edd01ad0e2dbfc05f7bdfaed46292e7b504a6ac7839aa00edb/jiter-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add5e227e0554d3a52cf390a7635edaffdf4f8fce4fdbcef3cc2055bb396a30c", size = 367243 }, + { url = "https://files.pythonhosted.org/packages/fd/76/0051b0ac2816253a99d27baf3dda198663aff882fa6ea7deeb94046da24e/jiter-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9b1cda8fcb736250d7e8711d4580ebf004a46771432be0ae4796944b5dfa5d", size = 479315 }, + { url = "https://files.pythonhosted.org/packages/70/ae/83f793acd68e5cb24e483f44f482a1a15601848b9b6f199dacb970098f77/jiter-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb12a2223fe0135c7ff1356a143d57f95bbf1f4a66584f1fc74df21d86b993", size = 380714 }, + { url = "https://files.pythonhosted.org/packages/b1/5e/4808a88338ad2c228b1126b93fcd8ba145e919e886fe910d578230dabe3b/jiter-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c596cc0f4cb574877550ce4ecd51f8037469146addd676d7c1a30ebe6391923f", size = 365168 }, + { url = "https://files.pythonhosted.org/packages/0c/d4/04619a9e8095b42aef436b5aeb4c0282b4ff1b27d1db1508df9f5dc82750/jiter-0.12.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ab4c823b216a4aeab3fdbf579c5843165756bd9ad87cc6b1c65919c4715f783", size = 387893 }, + { url = "https://files.pythonhosted.org/packages/17/ea/d3c7e62e4546fdc39197fa4a4315a563a89b95b6d54c0d25373842a59cbe/jiter-0.12.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e427eee51149edf962203ff8db75a7514ab89be5cb623fb9cea1f20b54f1107b", size = 520828 }, + { url = "https://files.pythonhosted.org/packages/cc/0b/c6d3562a03fd767e31cb119d9041ea7958c3c80cb3d753eafb19b3b18349/jiter-0.12.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:edb868841f84c111255ba5e80339d386d937ec1fdce419518ce1bd9370fac5b6", size = 511009 }, + { url = "https://files.pythonhosted.org/packages/aa/51/2cb4468b3448a8385ebcd15059d325c9ce67df4e2758d133ab9442b19834/jiter-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8bbcfe2791dfdb7c5e48baf646d37a6a3dcb5a97a032017741dea9f817dca183", size = 205110 }, + { url = "https://files.pythonhosted.org/packages/b2/c5/ae5ec83dec9c2d1af805fd5fe8f74ebded9c8670c5210ec7820ce0dbeb1e/jiter-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fa940963bf02e1d8226027ef461e36af472dea85d36054ff835aeed944dd873", size = 205223 }, + { url = "https://files.pythonhosted.org/packages/97/9a/3c5391907277f0e55195550cf3fa8e293ae9ee0c00fb402fec1e38c0c82f/jiter-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:506c9708dd29b27288f9f8f1140c3cb0e3d8ddb045956d7757b1fa0e0f39a473", size = 185564 }, + { url = "https://files.pythonhosted.org/packages/fe/54/5339ef1ecaa881c6948669956567a64d2670941925f245c434f494ffb0e5/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:4739a4657179ebf08f85914ce50332495811004cc1747852e8b2041ed2aab9b8", size = 311144 }, + { url = "https://files.pythonhosted.org/packages/27/74/3446c652bffbd5e81ab354e388b1b5fc1d20daac34ee0ed11ff096b1b01a/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:41da8def934bf7bec16cb24bd33c0ca62126d2d45d81d17b864bd5ad721393c3", size = 305877 }, + { url = "https://files.pythonhosted.org/packages/a1/f4/ed76ef9043450f57aac2d4fbeb27175aa0eb9c38f833be6ef6379b3b9a86/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c44ee814f499c082e69872d426b624987dbc5943ab06e9bbaa4f81989fdb79e", size = 340419 }, + { url = "https://files.pythonhosted.org/packages/21/01/857d4608f5edb0664aa791a3d45702e1a5bcfff9934da74035e7b9803846/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2097de91cf03eaa27b3cbdb969addf83f0179c6afc41bbc4513705e013c65d", size = 347212 }, + { url = "https://files.pythonhosted.org/packages/cb/f5/12efb8ada5f5c9edc1d4555fe383c1fb2eac05ac5859258a72d61981d999/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e8547883d7b96ef2e5fe22b88f8a4c8725a56e7f4abafff20fd5272d634c7ecb", size = 309974 }, + { url = "https://files.pythonhosted.org/packages/85/15/d6eb3b770f6a0d332675141ab3962fd4a7c270ede3515d9f3583e1d28276/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:89163163c0934854a668ed783a2546a0617f71706a2551a4a0666d91ab365d6b", size = 304233 }, + { url = "https://files.pythonhosted.org/packages/8c/3e/e7e06743294eea2cf02ced6aa0ff2ad237367394e37a0e2b4a1108c67a36/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96b264ab7d34bbb2312dedc47ce07cd53f06835eacbc16dde3761f47c3a9e7f", size = 338537 }, + { url = "https://files.pythonhosted.org/packages/2f/9c/6753e6522b8d0ef07d3a3d239426669e984fb0eba15a315cdbc1253904e4/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24e864cb30ab82311c6425655b0cdab0a98c5d973b065c66a3f020740c2324c", size = 346110 }, ] [[package]] @@ -890,7 +889,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.0.2" +version = "1.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -901,9 +900,9 @@ dependencies = [ { name = "tenacity" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/96/b298cb46643bb235240de7ec8d87d3b4ccefead6bf41ca3c48c7d5397e01/langchain_core-1.0.2.tar.gz", hash = "sha256:9aae1908cc00d50b88e812305e980e7fd06d07375590ce8da01c04037bdccd72", size = 769167 } +sdist = { url = "https://files.pythonhosted.org/packages/93/35/147544d3422464d13a8ef88f9e25cff25e02c985eb44f8c106503f56ad50/langchain_core-1.0.4.tar.gz", hash = "sha256:086d408bcbeedecb0b152201e0163b85e7a6d9b26e11a75cc577b7371291df4e", size = 776329 } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/54/3aed89938a42cf7115575c333647551e35adc380feed651105d2d86c22f5/langchain_core-1.0.2-py3-none-any.whl", hash = "sha256:1f4ab4a41fc2e135e5dd4b97a89af123bbdc535af8f1f0644e8e8801bc288a12", size = 469251 }, + { url = "https://files.pythonhosted.org/packages/8e/ac/7032e5eb1c147a3d8e0a21a70e77d7efbd6295c8ce4833b90f6ff1750da9/langchain_core-1.0.4-py3-none-any.whl", hash = "sha256:53caa351d9d73b56f5d9628980f36851cfa725977508098869fdc2d246da43b3", size = 471198 }, ] [[package]] @@ -920,7 +919,7 @@ wheels = [ [[package]] name = "langsmith" -version = "0.4.38" +version = "0.4.42" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -931,9 +930,9 @@ dependencies = [ { name = "requests-toolbelt" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/21/f1ba48412c64bf3bb8feb532fc9d247b396935b5d8242332d44a4195ec2d/langsmith-0.4.38.tar.gz", hash = "sha256:3aa57f9c16a5880256cd1eab0452533c1fb5ee14ec5250e23ed919cc2b07f6d3", size = 942789 } +sdist = { url = "https://files.pythonhosted.org/packages/c8/91/939cb2fa0317a8aedd0b4dab6c189722e5ef15abcf65304dc929e582826a/langsmith-0.4.42.tar.gz", hash = "sha256:a6e808e47581403cb019b47c8c10627c1644f78ed4c03fa877d6ad661476c38f", size = 953877 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/2b/7e0248f65e35800ea8e4e3dbb3bcc36c61b81f5b8abeddaceec8320ab491/langsmith-0.4.38-py3-none-any.whl", hash = "sha256:326232a24b1c6dd308a3188557cc023adf8fb14144263b2982c115a6be5141e7", size = 397341 }, + { url = "https://files.pythonhosted.org/packages/0f/17/4280bc381b40a642ea5efe1bab0237f03507a9d4281484c5baa1db82055a/langsmith-0.4.42-py3-none-any.whl", hash = "sha256:015b0a0c17eb1a61293e8cbb7d41778a4b37caddd267d54274ba94e4721b301b", size = 401937 }, ] [[package]] @@ -1094,23 +1093,6 @@ requires-dist = [ { name = "uvicorn", specifier = ">=0.38.0" }, ] -[[package]] -name = "memora-cli" -version = "0.1.0" -source = { editable = "memora-cli" } -dependencies = [ - { name = "httpx" }, - { name = "rich" }, - { name = "typer" }, -] - -[package.metadata] -requires-dist = [ - { name = "httpx", specifier = ">=0.27.0" }, - { name = "rich", specifier = ">=13.0.0" }, - { name = "typer", specifier = ">=0.20.0" }, -] - [[package]] name = "memora-dev" version = "0.1.0" @@ -1133,11 +1115,11 @@ wheels = [ [[package]] name = "narwhals" -version = "2.10.2" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c5/dc/8db74daf8c2690ec696c1d772a33cc01511559ee8a9e92d7ed85a18e3c22/narwhals-2.10.2.tar.gz", hash = "sha256:ff738a08bc993cbb792266bec15346c1d85cc68fdfe82a23283c3713f78bd354", size = 584954 } +sdist = { url = "https://files.pythonhosted.org/packages/7d/a2/25208347aa4c2d82a265cf4bc0873aaf5069f525c0438146821e7fc19ef5/narwhals-2.11.0.tar.gz", hash = "sha256:d23f3ea7efc6b4d0355444a72de6b8fa3011175585246c3400c894a7583964af", size = 589233 } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/a9/9e02fa97e421a355fc5e818e9c488080fce04a8e0eebb3ed75a84f041c4a/narwhals-2.10.2-py3-none-any.whl", hash = "sha256:059cd5c6751161b97baedcaf17a514c972af6a70f36a89af17de1a0caf519c43", size = 419573 }, + { url = "https://files.pythonhosted.org/packages/c0/a1/4d21933898e23b011ae0528151b57a9230a62960d0919bf2ee48c7f5c20a/narwhals-2.11.0-py3-none-any.whl", hash = "sha256:a9795e1e44aa94e5ba6406ef1c5ee4c172414ced4f1aea4a79e5894f0c7378d4", size = 423069 }, ] [[package]] @@ -1375,7 +1357,7 @@ wheels = [ [[package]] name = "openai" -version = "2.6.1" +version = "2.7.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1387,9 +1369,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/44/303deb97be7c1c9b53118b52825cbd1557aeeff510f3a52566b1fa66f6a2/openai-2.6.1.tar.gz", hash = "sha256:27ae704d190615fca0c0fc2b796a38f8b5879645a3a52c9c453b23f97141bb49", size = 593043 } +sdist = { url = "https://files.pythonhosted.org/packages/71/e3/cec27fa28ef36c4ccea71e9e8c20be9b8539618732989a82027575aab9d4/openai-2.7.2.tar.gz", hash = "sha256:082ef61163074d8efad0035dd08934cf5e3afd37254f70fc9165dd6a8c67dcbd", size = 595732 } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/0e/331df43df633e6105ff9cf45e0ce57762bd126a45ac16b25a43f6738d8a2/openai-2.6.1-py3-none-any.whl", hash = "sha256:904e4b5254a8416746a2f05649594fa41b19d799843cd134dac86167e094edef", size = 1005551 }, + { url = "https://files.pythonhosted.org/packages/25/66/22cfe4b695b5fd042931b32c67d685e867bfd169ebf46036b95b57314c33/openai-2.7.2-py3-none-any.whl", hash = "sha256:116f522f4427f8a0a59b51655a356da85ce092f3ed6abeca65f03c8be6e073d9", size = 1008375 }, ] [[package]] @@ -1736,7 +1718,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.12.3" +version = "2.12.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1744,9 +1726,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383 } +sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431 }, + { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400 }, ] [package.optional-dependencies] @@ -1756,95 +1738,99 @@ email = [ [[package]] name = "pydantic-core" -version = "2.41.4" +version = "2.41.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557 } +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952 } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/4c/f6cbfa1e8efacd00b846764e8484fe173d25b8dab881e277a619177f3384/pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80", size = 2109062 }, - { url = "https://files.pythonhosted.org/packages/21/f8/40b72d3868896bfcd410e1bd7e516e762d326201c48e5b4a06446f6cf9e8/pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae", size = 1916301 }, - { url = "https://files.pythonhosted.org/packages/94/4d/d203dce8bee7faeca791671c88519969d98d3b4e8f225da5b96dad226fc8/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827", size = 1968728 }, - { url = "https://files.pythonhosted.org/packages/65/f5/6a66187775df87c24d526985b3a5d78d861580ca466fbd9d4d0e792fcf6c/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f", size = 2050238 }, - { url = "https://files.pythonhosted.org/packages/5e/b9/78336345de97298cf53236b2f271912ce11f32c1e59de25a374ce12f9cce/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def", size = 2249424 }, - { url = "https://files.pythonhosted.org/packages/99/bb/a4584888b70ee594c3d374a71af5075a68654d6c780369df269118af7402/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2", size = 2366047 }, - { url = "https://files.pythonhosted.org/packages/5f/8d/17fc5de9d6418e4d2ae8c675f905cdafdc59d3bf3bf9c946b7ab796a992a/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8", size = 2071163 }, - { url = "https://files.pythonhosted.org/packages/54/e7/03d2c5c0b8ed37a4617430db68ec5e7dbba66358b629cd69e11b4d564367/pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265", size = 2190585 }, - { url = "https://files.pythonhosted.org/packages/be/fc/15d1c9fe5ad9266a5897d9b932b7f53d7e5cfc800573917a2c5d6eea56ec/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c", size = 2150109 }, - { url = "https://files.pythonhosted.org/packages/26/ef/e735dd008808226c83ba56972566138665b71477ad580fa5a21f0851df48/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a", size = 2315078 }, - { url = "https://files.pythonhosted.org/packages/90/00/806efdcf35ff2ac0f938362350cd9827b8afb116cc814b6b75cf23738c7c/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e", size = 2318737 }, - { url = "https://files.pythonhosted.org/packages/41/7e/6ac90673fe6cb36621a2283552897838c020db343fa86e513d3f563b196f/pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03", size = 1974160 }, - { url = "https://files.pythonhosted.org/packages/e0/9d/7c5e24ee585c1f8b6356e1d11d40ab807ffde44d2db3b7dfd6d20b09720e/pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e", size = 2021883 }, - { url = "https://files.pythonhosted.org/packages/33/90/5c172357460fc28b2871eb4a0fb3843b136b429c6fa827e4b588877bf115/pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db", size = 1968026 }, - { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043 }, - { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699 }, - { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121 }, - { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590 }, - { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869 }, - { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169 }, - { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165 }, - { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067 }, - { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997 }, - { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187 }, - { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204 }, - { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536 }, - { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132 }, - { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483 }, - { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688 }, - { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807 }, - { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669 }, - { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629 }, - { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049 }, - { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409 }, - { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635 }, - { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284 }, - { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566 }, - { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809 }, - { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119 }, - { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398 }, - { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735 }, - { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209 }, - { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324 }, - { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515 }, - { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819 }, - { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866 }, - { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034 }, - { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022 }, - { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495 }, - { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131 }, - { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236 }, - { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573 }, - { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467 }, - { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754 }, - { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754 }, - { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115 }, - { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400 }, - { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070 }, - { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277 }, - { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608 }, - { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614 }, - { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904 }, - { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538 }, - { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183 }, - { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542 }, - { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897 }, - { url = "https://files.pythonhosted.org/packages/b0/12/5ba58daa7f453454464f92b3ca7b9d7c657d8641c48e370c3ebc9a82dd78/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b", size = 2122139 }, - { url = "https://files.pythonhosted.org/packages/21/fb/6860126a77725c3108baecd10fd3d75fec25191d6381b6eb2ac660228eac/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42", size = 1936674 }, - { url = "https://files.pythonhosted.org/packages/de/be/57dcaa3ed595d81f8757e2b44a38240ac5d37628bce25fb20d02c7018776/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee", size = 1956398 }, - { url = "https://files.pythonhosted.org/packages/2f/1d/679a344fadb9695f1a6a294d739fbd21d71fa023286daeea8c0ed49e7c2b/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c", size = 2138674 }, - { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087 }, - { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387 }, - { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495 }, - { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008 }, - { url = "https://files.pythonhosted.org/packages/7e/7d/138e902ed6399b866f7cfe4435d22445e16fff888a1c00560d9dc79a780f/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5", size = 2104721 }, - { url = "https://files.pythonhosted.org/packages/47/13/0525623cf94627f7b53b4c2034c81edc8491cbfc7c28d5447fa318791479/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2", size = 1931608 }, - { url = "https://files.pythonhosted.org/packages/d6/f9/744bc98137d6ef0a233f808bfc9b18cf94624bf30836a18d3b05d08bf418/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd", size = 2132986 }, - { url = "https://files.pythonhosted.org/packages/17/c8/629e88920171173f6049386cc71f893dff03209a9ef32b4d2f7e7c264bcf/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c", size = 2187516 }, - { url = "https://files.pythonhosted.org/packages/2e/0f/4f2734688d98488782218ca61bcc118329bf5de05bb7fe3adc7dd79b0b86/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405", size = 2146146 }, - { url = "https://files.pythonhosted.org/packages/ed/f2/ab385dbd94a052c62224b99cf99002eee99dbec40e10006c78575aead256/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8", size = 2311296 }, - { url = "https://files.pythonhosted.org/packages/fc/8e/e4f12afe1beeb9823bba5375f8f258df0cc61b056b0195fb1cf9f62a1a58/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308", size = 2315386 }, - { url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f", size = 2147775 }, + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873 }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826 }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869 }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890 }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740 }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021 }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378 }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761 }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303 }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355 }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875 }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549 }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305 }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902 }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990 }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003 }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200 }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578 }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504 }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816 }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366 }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698 }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603 }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591 }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068 }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908 }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145 }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179 }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403 }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206 }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307 }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258 }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917 }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186 }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164 }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146 }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788 }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133 }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852 }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679 }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766 }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005 }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622 }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725 }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040 }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691 }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897 }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302 }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877 }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680 }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960 }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102 }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039 }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126 }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489 }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288 }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255 }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760 }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092 }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385 }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832 }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585 }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078 }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914 }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560 }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244 }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955 }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906 }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607 }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769 }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441 }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291 }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632 }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905 }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495 }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388 }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879 }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017 }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980 }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865 }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256 }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762 }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141 }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317 }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992 }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302 }, ] [[package]] @@ -1871,7 +1857,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1880,22 +1866,22 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618 } +sdist = { url = "https://files.pythonhosted.org/packages/da/1d/eb34f286b164c5e431a810a38697409cca1112cee04b287bb56ac486730b/pytest-9.0.0.tar.gz", hash = "sha256:8f44522eafe4137b0f35c9ce3072931a788a21ee40a2ed279e817d3cc16ed21e", size = 1562764 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750 }, + { url = "https://files.pythonhosted.org/packages/72/99/cafef234114a3b6d9f3aaed0723b437c40c57bdb7b3e4c3a575bc4890052/pytest-9.0.0-py3-none-any.whl", hash = "sha256:e5ccdf10b0bac554970ee88fc1a4ad0ee5d221f8ef22321f9b7e4584e19d7f96", size = 373364 }, ] [[package]] name = "pytest-asyncio" -version = "1.2.0" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119 } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095 }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075 }, ] [[package]] @@ -2041,94 +2027,94 @@ wheels = [ [[package]] name = "regex" -version = "2025.10.23" +version = "2025.11.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/c8/1d2160d36b11fbe0a61acb7c3c81ab032d9ec8ad888ac9e0a61b85ab99dd/regex-2025.10.23.tar.gz", hash = "sha256:8cbaf8ceb88f96ae2356d01b9adf5e6306fa42fa6f7eab6b97794e37c959ac26", size = 401266 } +sdist = { url = "https://files.pythonhosted.org/packages/cc/a9/546676f25e573a4cf00fe8e119b78a37b6a8fe2dc95cda877b30889c9c45/regex-2025.11.3.tar.gz", hash = "sha256:1fedc720f9bb2494ce31a58a1631f9c82df6a09b49c19517ea5cc280b4541e01", size = 414669 } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/e5/74b7cd5cd76b4171f9793042045bb1726f7856dd56e582fc3e058a7a8a5e/regex-2025.10.23-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c531155bf9179345e85032052a1e5fe1a696a6abf9cea54b97e8baefff970fd", size = 487960 }, - { url = "https://files.pythonhosted.org/packages/b9/08/854fa4b3b20471d1df1c71e831b6a1aa480281e37791e52a2df9641ec5c6/regex-2025.10.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:912e9df4e89d383681268d38ad8f5780d7cccd94ba0e9aa09ca7ab7ab4f8e7eb", size = 290425 }, - { url = "https://files.pythonhosted.org/packages/ab/d3/6272b1dd3ca1271661e168762b234ad3e00dbdf4ef0c7b9b72d2d159efa7/regex-2025.10.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f375c61bfc3138b13e762fe0ae76e3bdca92497816936534a0177201666f44f", size = 288278 }, - { url = "https://files.pythonhosted.org/packages/14/8f/c7b365dd9d9bc0a36e018cb96f2ffb60d2ba8deb589a712b437f67de2920/regex-2025.10.23-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e248cc9446081119128ed002a3801f8031e0c219b5d3c64d3cc627da29ac0a33", size = 793289 }, - { url = "https://files.pythonhosted.org/packages/d4/fb/b8fbe9aa16cf0c21f45ec5a6c74b4cecbf1a1c0deb7089d4a6f83a9c1caa/regex-2025.10.23-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b52bf9282fdf401e4f4e721f0f61fc4b159b1307244517789702407dd74e38ca", size = 860321 }, - { url = "https://files.pythonhosted.org/packages/b0/81/bf41405c772324926a9bd8a640dedaa42da0e929241834dfce0733070437/regex-2025.10.23-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c084889ab2c59765a0d5ac602fd1c3c244f9b3fcc9a65fdc7ba6b74c5287490", size = 907011 }, - { url = "https://files.pythonhosted.org/packages/a4/fb/5ad6a8b92d3f88f3797b51bb4ef47499acc2d0b53d2fbe4487a892f37a73/regex-2025.10.23-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80e8eb79009bdb0936658c44ca06e2fbbca67792013e3818eea3f5f228971c2", size = 800312 }, - { url = "https://files.pythonhosted.org/packages/42/48/b4efba0168a2b57f944205d823f8e8a3a1ae6211a34508f014ec2c712f4f/regex-2025.10.23-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6f259118ba87b814a8ec475380aee5f5ae97a75852a3507cf31d055b01b5b40", size = 782839 }, - { url = "https://files.pythonhosted.org/packages/13/2a/c9efb4c6c535b0559c1fa8e431e0574d229707c9ca718600366fcfef6801/regex-2025.10.23-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9b8c72a242683dcc72d37595c4f1278dfd7642b769e46700a8df11eab19dfd82", size = 854270 }, - { url = "https://files.pythonhosted.org/packages/34/2d/68eecc1bdaee020e8ba549502291c9450d90d8590d0552247c9b543ebf7b/regex-2025.10.23-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d7b7a0a3df9952f9965342159e0c1f05384c0f056a47ce8b61034f8cecbe83", size = 845771 }, - { url = "https://files.pythonhosted.org/packages/a5/cd/a1ae499cf9b87afb47a67316bbf1037a7c681ffe447c510ed98c0aa2c01c/regex-2025.10.23-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:413bfea20a484c524858125e92b9ce6ffdd0a4b97d4ff96b5859aa119b0f1bdd", size = 788778 }, - { url = "https://files.pythonhosted.org/packages/38/f9/70765e63f5ea7d43b2b6cd4ee9d3323f16267e530fb2a420d92d991cf0fc/regex-2025.10.23-cp311-cp311-win32.whl", hash = "sha256:f76deef1f1019a17dad98f408b8f7afc4bd007cbe835ae77b737e8c7f19ae575", size = 265666 }, - { url = "https://files.pythonhosted.org/packages/9c/1a/18e9476ee1b63aaec3844d8e1cb21842dc19272c7e86d879bfc0dcc60db3/regex-2025.10.23-cp311-cp311-win_amd64.whl", hash = "sha256:59bba9f7125536f23fdab5deeea08da0c287a64c1d3acc1c7e99515809824de8", size = 277600 }, - { url = "https://files.pythonhosted.org/packages/1d/1b/c019167b1f7a8ec77251457e3ff0339ed74ca8bce1ea13138dc98309c923/regex-2025.10.23-cp311-cp311-win_arm64.whl", hash = "sha256:b103a752b6f1632ca420225718d6ed83f6a6ced3016dd0a4ab9a6825312de566", size = 269974 }, - { url = "https://files.pythonhosted.org/packages/f6/57/eeb274d83ab189d02d778851b1ac478477522a92b52edfa6e2ae9ff84679/regex-2025.10.23-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7a44d9c00f7a0a02d3b777429281376370f3d13d2c75ae74eb94e11ebcf4a7fc", size = 489187 }, - { url = "https://files.pythonhosted.org/packages/55/5c/7dad43a9b6ea88bf77e0b8b7729a4c36978e1043165034212fd2702880c6/regex-2025.10.23-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b83601f84fde939ae3478bb32a3aef36f61b58c3208d825c7e8ce1a735f143f2", size = 291122 }, - { url = "https://files.pythonhosted.org/packages/66/21/38b71e6f2818f0f4b281c8fba8d9d57cfca7b032a648fa59696e0a54376a/regex-2025.10.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec13647907bb9d15fd192bbfe89ff06612e098a5709e7d6ecabbdd8f7908fc45", size = 288797 }, - { url = "https://files.pythonhosted.org/packages/be/95/888f069c89e7729732a6d7cca37f76b44bfb53a1e35dda8a2c7b65c1b992/regex-2025.10.23-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78d76dd2957d62501084e7012ddafc5fcd406dd982b7a9ca1ea76e8eaaf73e7e", size = 798442 }, - { url = "https://files.pythonhosted.org/packages/76/70/4f903c608faf786627a8ee17c06e0067b5acade473678b69c8094b248705/regex-2025.10.23-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8668e5f067e31a47699ebb354f43aeb9c0ef136f915bd864243098524482ac43", size = 864039 }, - { url = "https://files.pythonhosted.org/packages/62/19/2df67b526bf25756c7f447dde554fc10a220fd839cc642f50857d01e4a7b/regex-2025.10.23-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a32433fe3deb4b2d8eda88790d2808fed0dc097e84f5e683b4cd4f42edef6cca", size = 912057 }, - { url = "https://files.pythonhosted.org/packages/99/14/9a39b7c9e007968411bc3c843cc14cf15437510c0a9991f080cab654fd16/regex-2025.10.23-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d97d73818c642c938db14c0668167f8d39520ca9d983604575ade3fda193afcc", size = 803374 }, - { url = "https://files.pythonhosted.org/packages/d4/f7/3495151dd3ca79949599b6d069b72a61a2c5e24fc441dccc79dcaf708fe6/regex-2025.10.23-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bca7feecc72ee33579e9f6ddf8babbe473045717a0e7dbc347099530f96e8b9a", size = 787714 }, - { url = "https://files.pythonhosted.org/packages/28/65/ee882455e051131869957ee8597faea45188c9a98c0dad724cfb302d4580/regex-2025.10.23-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7e24af51e907d7457cc4a72691ec458320b9ae67dc492f63209f01eecb09de32", size = 858392 }, - { url = "https://files.pythonhosted.org/packages/53/25/9287fef5be97529ebd3ac79d256159cb709a07eb58d4be780d1ca3885da8/regex-2025.10.23-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d10bcde58bbdf18146f3a69ec46dd03233b94a4a5632af97aa5378da3a47d288", size = 850484 }, - { url = "https://files.pythonhosted.org/packages/f3/b4/b49b88b4fea2f14dc73e5b5842755e782fc2e52f74423d6f4adc130d5880/regex-2025.10.23-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:44383bc0c933388516c2692c9a7503e1f4a67e982f20b9a29d2fb70c6494f147", size = 789634 }, - { url = "https://files.pythonhosted.org/packages/b6/3c/2f8d199d0e84e78bcd6bdc2be9b62410624f6b796e2893d1837ae738b160/regex-2025.10.23-cp312-cp312-win32.whl", hash = "sha256:6040a86f95438a0114bba16e51dfe27f1bc004fd29fe725f54a586f6d522b079", size = 266060 }, - { url = "https://files.pythonhosted.org/packages/d7/67/c35e80969f6ded306ad70b0698863310bdf36aca57ad792f45ddc0e2271f/regex-2025.10.23-cp312-cp312-win_amd64.whl", hash = "sha256:436b4c4352fe0762e3bfa34a5567079baa2ef22aa9c37cf4d128979ccfcad842", size = 276931 }, - { url = "https://files.pythonhosted.org/packages/f5/a1/4ed147de7d2b60174f758412c87fa51ada15cd3296a0ff047f4280aaa7ca/regex-2025.10.23-cp312-cp312-win_arm64.whl", hash = "sha256:f4b1b1991617055b46aff6f6db24888c1f05f4db9801349d23f09ed0714a9335", size = 270103 }, - { url = "https://files.pythonhosted.org/packages/28/c6/195a6217a43719d5a6a12cc192a22d12c40290cecfa577f00f4fb822f07d/regex-2025.10.23-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b7690f95404a1293923a296981fd943cca12c31a41af9c21ba3edd06398fc193", size = 488956 }, - { url = "https://files.pythonhosted.org/packages/4c/93/181070cd1aa2fa541ff2d3afcf763ceecd4937b34c615fa92765020a6c90/regex-2025.10.23-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1a32d77aeaea58a13230100dd8797ac1a84c457f3af2fdf0d81ea689d5a9105b", size = 290997 }, - { url = "https://files.pythonhosted.org/packages/b6/c5/9d37fbe3a40ed8dda78c23e1263002497540c0d1522ed75482ef6c2000f0/regex-2025.10.23-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b24b29402f264f70a3c81f45974323b41764ff7159655360543b7cabb73e7d2f", size = 288686 }, - { url = "https://files.pythonhosted.org/packages/5f/e7/db610ff9f10c2921f9b6ac0c8d8be4681b28ddd40fc0549429366967e61f/regex-2025.10.23-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:563824a08c7c03d96856d84b46fdb3bbb7cfbdf79da7ef68725cda2ce169c72a", size = 798466 }, - { url = "https://files.pythonhosted.org/packages/90/10/aab883e1fa7fe2feb15ac663026e70ca0ae1411efa0c7a4a0342d9545015/regex-2025.10.23-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0ec8bdd88d2e2659c3518087ee34b37e20bd169419ffead4240a7004e8ed03b", size = 863996 }, - { url = "https://files.pythonhosted.org/packages/a2/b0/8f686dd97a51f3b37d0238cd00a6d0f9ccabe701f05b56de1918571d0d61/regex-2025.10.23-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b577601bfe1d33913fcd9276d7607bbac827c4798d9e14d04bf37d417a6c41cb", size = 912145 }, - { url = "https://files.pythonhosted.org/packages/a3/ca/639f8cd5b08797bca38fc5e7e07f76641a428cf8c7fca05894caf045aa32/regex-2025.10.23-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c9f2c68ac6cb3de94eea08a437a75eaa2bd33f9e97c84836ca0b610a5804368", size = 803370 }, - { url = "https://files.pythonhosted.org/packages/0d/1e/a40725bb76959eddf8abc42a967bed6f4851b39f5ac4f20e9794d7832aa5/regex-2025.10.23-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89f8b9ea3830c79468e26b0e21c3585f69f105157c2154a36f6b7839f8afb351", size = 787767 }, - { url = "https://files.pythonhosted.org/packages/3d/d8/8ee9858062936b0f99656dce390aa667c6e7fb0c357b1b9bf76fb5e2e708/regex-2025.10.23-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:98fd84c4e4ea185b3bb5bf065261ab45867d8875032f358a435647285c722673", size = 858335 }, - { url = "https://files.pythonhosted.org/packages/d8/0a/ed5faaa63fa8e3064ab670e08061fbf09e3a10235b19630cf0cbb9e48c0a/regex-2025.10.23-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1e11d3e5887b8b096f96b4154dfb902f29c723a9556639586cd140e77e28b313", size = 850402 }, - { url = "https://files.pythonhosted.org/packages/79/14/d05f617342f4b2b4a23561da500ca2beab062bfcc408d60680e77ecaf04d/regex-2025.10.23-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f13450328a6634348d47a88367e06b64c9d84980ef6a748f717b13f8ce64e87", size = 789739 }, - { url = "https://files.pythonhosted.org/packages/f9/7b/e8ce8eef42a15f2c3461f8b3e6e924bbc86e9605cb534a393aadc8d3aff8/regex-2025.10.23-cp313-cp313-win32.whl", hash = "sha256:37be9296598a30c6a20236248cb8b2c07ffd54d095b75d3a2a2ee5babdc51df1", size = 266054 }, - { url = "https://files.pythonhosted.org/packages/71/2d/55184ed6be6473187868d2f2e6a0708195fc58270e62a22cbf26028f2570/regex-2025.10.23-cp313-cp313-win_amd64.whl", hash = "sha256:ea7a3c283ce0f06fe789365841e9174ba05f8db16e2fd6ae00a02df9572c04c0", size = 276917 }, - { url = "https://files.pythonhosted.org/packages/9c/d4/927eced0e2bd45c45839e556f987f8c8f8683268dd3c00ad327deb3b0172/regex-2025.10.23-cp313-cp313-win_arm64.whl", hash = "sha256:d9a4953575f300a7bab71afa4cd4ac061c7697c89590a2902b536783eeb49a4f", size = 270105 }, - { url = "https://files.pythonhosted.org/packages/3e/b3/95b310605285573341fc062d1d30b19a54f857530e86c805f942c4ff7941/regex-2025.10.23-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7d6606524fa77b3912c9ef52a42ef63c6cfbfc1077e9dc6296cd5da0da286044", size = 491850 }, - { url = "https://files.pythonhosted.org/packages/a4/8f/207c2cec01e34e56db1eff606eef46644a60cf1739ecd474627db90ad90b/regex-2025.10.23-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c037aadf4d64bdc38af7db3dbd34877a057ce6524eefcb2914d6d41c56f968cc", size = 292537 }, - { url = "https://files.pythonhosted.org/packages/98/3b/025240af4ada1dc0b5f10d73f3e5122d04ce7f8908ab8881e5d82b9d61b6/regex-2025.10.23-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:99018c331fb2529084a0c9b4c713dfa49fafb47c7712422e49467c13a636c656", size = 290904 }, - { url = "https://files.pythonhosted.org/packages/81/8e/104ac14e2d3450c43db18ec03e1b96b445a94ae510b60138f00ce2cb7ca1/regex-2025.10.23-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd8aba965604d70306eb90a35528f776e59112a7114a5162824d43b76fa27f58", size = 807311 }, - { url = "https://files.pythonhosted.org/packages/19/63/78aef90141b7ce0be8a18e1782f764f6997ad09de0e05251f0d2503a914a/regex-2025.10.23-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:238e67264b4013e74136c49f883734f68656adf8257bfa13b515626b31b20f8e", size = 873241 }, - { url = "https://files.pythonhosted.org/packages/b3/a8/80eb1201bb49ae4dba68a1b284b4211ed9daa8e74dc600018a10a90399fb/regex-2025.10.23-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b2eb48bd9848d66fd04826382f5e8491ae633de3233a3d64d58ceb4ecfa2113a", size = 914794 }, - { url = "https://files.pythonhosted.org/packages/f0/d5/1984b6ee93281f360a119a5ca1af6a8ca7d8417861671388bf750becc29b/regex-2025.10.23-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d36591ce06d047d0c0fe2fc5f14bfbd5b4525d08a7b6a279379085e13f0e3d0e", size = 812581 }, - { url = "https://files.pythonhosted.org/packages/c4/39/11ebdc6d9927172a64ae237d16763145db6bd45ebb4055c17b88edab72a7/regex-2025.10.23-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5d4ece8628d6e364302006366cea3ee887db397faebacc5dacf8ef19e064cf8", size = 795346 }, - { url = "https://files.pythonhosted.org/packages/3b/b4/89a591bcc08b5e436af43315284bd233ba77daf0cf20e098d7af12f006c1/regex-2025.10.23-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:39a7e8083959cb1c4ff74e483eecb5a65d3b3e1d821b256e54baf61782c906c6", size = 868214 }, - { url = "https://files.pythonhosted.org/packages/3d/ff/58ba98409c1dbc8316cdb20dafbc63ed267380a07780cafecaf5012dabc9/regex-2025.10.23-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:842d449a8fefe546f311656cf8c0d6729b08c09a185f1cad94c756210286d6a8", size = 854540 }, - { url = "https://files.pythonhosted.org/packages/9a/f2/4a9e9338d67626e2071b643f828a482712ad15889d7268e11e9a63d6f7e9/regex-2025.10.23-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d614986dc68506be8f00474f4f6960e03e4ca9883f7df47744800e7d7c08a494", size = 799346 }, - { url = "https://files.pythonhosted.org/packages/63/be/543d35c46bebf6f7bf2be538cca74d6585f25714700c36f37f01b92df551/regex-2025.10.23-cp313-cp313t-win32.whl", hash = "sha256:a5b7a26b51a9df473ec16a1934d117443a775ceb7b39b78670b2e21893c330c9", size = 268657 }, - { url = "https://files.pythonhosted.org/packages/14/9f/4dd6b7b612037158bb2c9bcaa710e6fb3c40ad54af441b9c53b3a137a9f1/regex-2025.10.23-cp313-cp313t-win_amd64.whl", hash = "sha256:ce81c5544a5453f61cb6f548ed358cfb111e3b23f3cd42d250a4077a6be2a7b6", size = 280075 }, - { url = "https://files.pythonhosted.org/packages/81/7a/5bd0672aa65d38c8da6747c17c8b441bdb53d816c569e3261013af8e83cf/regex-2025.10.23-cp313-cp313t-win_arm64.whl", hash = "sha256:e9bf7f6699f490e4e43c44757aa179dab24d1960999c84ab5c3d5377714ed473", size = 271219 }, - { url = "https://files.pythonhosted.org/packages/73/f6/0caf29fec943f201fbc8822879c99d31e59c1d51a983d9843ee5cf398539/regex-2025.10.23-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5b5cb5b6344c4c4c24b2dc87b0bfee78202b07ef7633385df70da7fcf6f7cec6", size = 488960 }, - { url = "https://files.pythonhosted.org/packages/8e/7d/ebb7085b8fa31c24ce0355107cea2b92229d9050552a01c5d291c42aecea/regex-2025.10.23-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a6ce7973384c37bdf0f371a843f95a6e6f4e1489e10e0cf57330198df72959c5", size = 290932 }, - { url = "https://files.pythonhosted.org/packages/27/41/43906867287cbb5ca4cee671c3cc8081e15deef86a8189c3aad9ac9f6b4d/regex-2025.10.23-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2ee3663f2c334959016b56e3bd0dd187cbc73f948e3a3af14c3caaa0c3035d10", size = 288766 }, - { url = "https://files.pythonhosted.org/packages/ab/9e/ea66132776700fc77a39b1056e7a5f1308032fead94507e208dc6716b7cd/regex-2025.10.23-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2003cc82a579107e70d013482acce8ba773293f2db534fb532738395c557ff34", size = 798884 }, - { url = "https://files.pythonhosted.org/packages/d5/99/aed1453687ab63819a443930770db972c5c8064421f0d9f5da9ad029f26b/regex-2025.10.23-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:182c452279365a93a9f45874f7f191ec1c51e1f1eb41bf2b16563f1a40c1da3a", size = 864768 }, - { url = "https://files.pythonhosted.org/packages/99/5d/732fe747a1304805eb3853ce6337eea16b169f7105a0d0dd9c6a5ffa9948/regex-2025.10.23-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b1249e9ff581c5b658c8f0437f883b01f1edcf424a16388591e7c05e5e9e8b0c", size = 911394 }, - { url = "https://files.pythonhosted.org/packages/5e/48/58a1f6623466522352a6efa153b9a3714fc559d9f930e9bc947b4a88a2c3/regex-2025.10.23-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b841698f93db3ccc36caa1900d2a3be281d9539b822dc012f08fc80b46a3224", size = 803145 }, - { url = "https://files.pythonhosted.org/packages/ea/f6/7dea79be2681a5574ab3fc237aa53b2c1dfd6bd2b44d4640b6c76f33f4c1/regex-2025.10.23-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:956d89e0c92d471e8f7eee73f73fdff5ed345886378c45a43175a77538a1ffe4", size = 787831 }, - { url = "https://files.pythonhosted.org/packages/3a/ad/07b76950fbbe65f88120ca2d8d845047c401450f607c99ed38862904671d/regex-2025.10.23-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5c259cb363299a0d90d63b5c0d7568ee98419861618a95ee9d91a41cb9954462", size = 859162 }, - { url = "https://files.pythonhosted.org/packages/41/87/374f3b2021b22aa6a4fc0b750d63f9721e53d1631a238f7a1c343c1cd288/regex-2025.10.23-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:185d2b18c062820b3a40d8fefa223a83f10b20a674bf6e8c4a432e8dfd844627", size = 849899 }, - { url = "https://files.pythonhosted.org/packages/12/4a/7f7bb17c5a5a9747249807210e348450dab9212a46ae6d23ebce86ba6a2b/regex-2025.10.23-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:281d87fa790049c2b7c1b4253121edd80b392b19b5a3d28dc2a77579cb2a58ec", size = 789372 }, - { url = "https://files.pythonhosted.org/packages/c9/dd/9c7728ff544fea09bbc8635e4c9e7c423b11c24f1a7a14e6ac4831466709/regex-2025.10.23-cp314-cp314-win32.whl", hash = "sha256:63b81eef3656072e4ca87c58084c7a9c2b81d41a300b157be635a8a675aacfb8", size = 271451 }, - { url = "https://files.pythonhosted.org/packages/48/f8/ef7837ff858eb74079c4804c10b0403c0b740762e6eedba41062225f7117/regex-2025.10.23-cp314-cp314-win_amd64.whl", hash = "sha256:0967c5b86f274800a34a4ed862dfab56928144d03cb18821c5153f8777947796", size = 280173 }, - { url = "https://files.pythonhosted.org/packages/8e/d0/d576e1dbd9885bfcd83d0e90762beea48d9373a6f7ed39170f44ed22e336/regex-2025.10.23-cp314-cp314-win_arm64.whl", hash = "sha256:c70dfe58b0a00b36aa04cdb0f798bf3e0adc31747641f69e191109fd8572c9a9", size = 273206 }, - { url = "https://files.pythonhosted.org/packages/a6/d0/2025268315e8b2b7b660039824cb7765a41623e97d4cd421510925400487/regex-2025.10.23-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1f5799ea1787aa6de6c150377d11afad39a38afd033f0c5247aecb997978c422", size = 491854 }, - { url = "https://files.pythonhosted.org/packages/44/35/5681c2fec5e8b33454390af209c4353dfc44606bf06d714b0b8bd0454ffe/regex-2025.10.23-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a9639ab7540cfea45ef57d16dcbea2e22de351998d614c3ad2f9778fa3bdd788", size = 292542 }, - { url = "https://files.pythonhosted.org/packages/5d/17/184eed05543b724132e4a18149e900f5189001fcfe2d64edaae4fbaf36b4/regex-2025.10.23-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:08f52122c352eb44c3421dab78b9b73a8a77a282cc8314ae576fcaa92b780d10", size = 290903 }, - { url = "https://files.pythonhosted.org/packages/25/d0/5e3347aa0db0de382dddfa133a7b0ae72f24b4344f3989398980b44a3924/regex-2025.10.23-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebf1baebef1c4088ad5a5623decec6b52950f0e4d7a0ae4d48f0a99f8c9cb7d7", size = 807546 }, - { url = "https://files.pythonhosted.org/packages/d2/bb/40c589bbdce1be0c55e9f8159789d58d47a22014f2f820cf2b517a5cd193/regex-2025.10.23-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:16b0f1c2e2d566c562d5c384c2b492646be0a19798532fdc1fdedacc66e3223f", size = 873322 }, - { url = "https://files.pythonhosted.org/packages/fe/56/a7e40c01575ac93360e606278d359f91829781a9f7fb6e5aa435039edbda/regex-2025.10.23-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7ada5d9dceafaab92646aa00c10a9efd9b09942dd9b0d7c5a4b73db92cc7e61", size = 914855 }, - { url = "https://files.pythonhosted.org/packages/5c/4b/d55587b192763db3163c3f508b3b67b31bb6f5e7a0e08b83013d0a59500a/regex-2025.10.23-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a36b4005770044bf08edecc798f0e41a75795b9e7c9c12fe29da8d792ef870c", size = 812724 }, - { url = "https://files.pythonhosted.org/packages/33/20/18bac334955fbe99d17229f4f8e98d05e4a501ac03a442be8facbb37c304/regex-2025.10.23-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:af7b2661dcc032da1fae82069b5ebf2ac1dfcd5359ef8b35e1367bfc92181432", size = 795439 }, - { url = "https://files.pythonhosted.org/packages/67/46/c57266be9df8549c7d85deb4cb82280cb0019e46fff677534c5fa1badfa4/regex-2025.10.23-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1cb976810ac1416a67562c2e5ba0accf6f928932320fef302e08100ed681b38e", size = 868336 }, - { url = "https://files.pythonhosted.org/packages/b8/f3/bd5879e41ef8187fec5e678e94b526a93f99e7bbe0437b0f2b47f9101694/regex-2025.10.23-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:1a56a54be3897d62f54290190fbcd754bff6932934529fbf5b29933da28fcd43", size = 854567 }, - { url = "https://files.pythonhosted.org/packages/e6/57/2b6bbdbd2f24dfed5b028033aa17ad8f7d86bb28f1a892cac8b3bc89d059/regex-2025.10.23-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8f3e6d202fb52c2153f532043bbcf618fd177df47b0b306741eb9b60ba96edc3", size = 799565 }, - { url = "https://files.pythonhosted.org/packages/c7/ba/a6168f542ba73b151ed81237adf6b869c7b2f7f8d51618111296674e20ee/regex-2025.10.23-cp314-cp314t-win32.whl", hash = "sha256:1fa1186966b2621b1769fd467c7b22e317e6ba2d2cdcecc42ea3089ef04a8521", size = 274428 }, - { url = "https://files.pythonhosted.org/packages/ef/a0/c84475e14a2829e9b0864ebf77c3f7da909df9d8acfe2bb540ff0072047c/regex-2025.10.23-cp314-cp314t-win_amd64.whl", hash = "sha256:08a15d40ce28362eac3e78e83d75475147869c1ff86bc93285f43b4f4431a741", size = 284140 }, - { url = "https://files.pythonhosted.org/packages/51/33/6a08ade0eee5b8ba79386869fa6f77afeb835b60510f3525db987e2fffc4/regex-2025.10.23-cp314-cp314t-win_arm64.whl", hash = "sha256:a93e97338e1c8ea2649e130dcfbe8cd69bba5e1e163834752ab64dcb4de6d5ed", size = 274497 }, + { url = "https://files.pythonhosted.org/packages/f7/90/4fb5056e5f03a7048abd2b11f598d464f0c167de4f2a51aa868c376b8c70/regex-2025.11.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eadade04221641516fa25139273505a1c19f9bf97589a05bc4cfcd8b4a618031", size = 488081 }, + { url = "https://files.pythonhosted.org/packages/85/23/63e481293fac8b069d84fba0299b6666df720d875110efd0338406b5d360/regex-2025.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:feff9e54ec0dd3833d659257f5c3f5322a12eee58ffa360984b716f8b92983f4", size = 290554 }, + { url = "https://files.pythonhosted.org/packages/2b/9d/b101d0262ea293a0066b4522dfb722eb6a8785a8c3e084396a5f2c431a46/regex-2025.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3b30bc921d50365775c09a7ed446359e5c0179e9e2512beec4a60cbcef6ddd50", size = 288407 }, + { url = "https://files.pythonhosted.org/packages/0c/64/79241c8209d5b7e00577ec9dca35cd493cc6be35b7d147eda367d6179f6d/regex-2025.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f99be08cfead2020c7ca6e396c13543baea32343b7a9a5780c462e323bd8872f", size = 793418 }, + { url = "https://files.pythonhosted.org/packages/3d/e2/23cd5d3573901ce8f9757c92ca4db4d09600b865919b6d3e7f69f03b1afd/regex-2025.11.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6dd329a1b61c0ee95ba95385fb0c07ea0d3fe1a21e1349fa2bec272636217118", size = 860448 }, + { url = "https://files.pythonhosted.org/packages/2a/4c/aecf31beeaa416d0ae4ecb852148d38db35391aac19c687b5d56aedf3a8b/regex-2025.11.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c5238d32f3c5269d9e87be0cf096437b7622b6920f5eac4fd202468aaeb34d2", size = 907139 }, + { url = "https://files.pythonhosted.org/packages/61/22/b8cb00df7d2b5e0875f60628594d44dba283e951b1ae17c12f99e332cc0a/regex-2025.11.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10483eefbfb0adb18ee9474498c9a32fcf4e594fbca0543bb94c48bac6183e2e", size = 800439 }, + { url = "https://files.pythonhosted.org/packages/02/a8/c4b20330a5cdc7a8eb265f9ce593f389a6a88a0c5f280cf4d978f33966bc/regex-2025.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78c2d02bb6e1da0720eedc0bad578049cad3f71050ef8cd065ecc87691bed2b0", size = 782965 }, + { url = "https://files.pythonhosted.org/packages/b4/4c/ae3e52988ae74af4b04d2af32fee4e8077f26e51b62ec2d12d246876bea2/regex-2025.11.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e6b49cd2aad93a1790ce9cffb18964f6d3a4b0b3dbdbd5de094b65296fce6e58", size = 854398 }, + { url = "https://files.pythonhosted.org/packages/06/d1/a8b9cf45874eda14b2e275157ce3b304c87e10fb38d9fc26a6e14eb18227/regex-2025.11.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:885b26aa3ee56433b630502dc3d36ba78d186a00cc535d3806e6bfd9ed3c70ab", size = 845897 }, + { url = "https://files.pythonhosted.org/packages/ea/fe/1830eb0236be93d9b145e0bd8ab499f31602fe0999b1f19e99955aa8fe20/regex-2025.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ddd76a9f58e6a00f8772e72cff8ebcff78e022be95edf018766707c730593e1e", size = 788906 }, + { url = "https://files.pythonhosted.org/packages/66/47/dc2577c1f95f188c1e13e2e69d8825a5ac582ac709942f8a03af42ed6e93/regex-2025.11.3-cp311-cp311-win32.whl", hash = "sha256:3e816cc9aac1cd3cc9a4ec4d860f06d40f994b5c7b4d03b93345f44e08cc68bf", size = 265812 }, + { url = "https://files.pythonhosted.org/packages/50/1e/15f08b2f82a9bbb510621ec9042547b54d11e83cb620643ebb54e4eb7d71/regex-2025.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:087511f5c8b7dfbe3a03f5d5ad0c2a33861b1fc387f21f6f60825a44865a385a", size = 277737 }, + { url = "https://files.pythonhosted.org/packages/f4/fc/6500eb39f5f76c5e47a398df82e6b535a5e345f839581012a418b16f9cc3/regex-2025.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:1ff0d190c7f68ae7769cd0313fe45820ba07ffebfddfaa89cc1eb70827ba0ddc", size = 270290 }, + { url = "https://files.pythonhosted.org/packages/e8/74/18f04cb53e58e3fb107439699bd8375cf5a835eec81084e0bddbd122e4c2/regex-2025.11.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bc8ab71e2e31b16e40868a40a69007bc305e1109bd4658eb6cad007e0bf67c41", size = 489312 }, + { url = "https://files.pythonhosted.org/packages/78/3f/37fcdd0d2b1e78909108a876580485ea37c91e1acf66d3bb8e736348f441/regex-2025.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:22b29dda7e1f7062a52359fca6e58e548e28c6686f205e780b02ad8ef710de36", size = 291256 }, + { url = "https://files.pythonhosted.org/packages/bf/26/0a575f58eb23b7ebd67a45fccbc02ac030b737b896b7e7a909ffe43ffd6a/regex-2025.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a91e4a29938bc1a082cc28fdea44be420bf2bebe2665343029723892eb073e1", size = 288921 }, + { url = "https://files.pythonhosted.org/packages/ea/98/6a8dff667d1af907150432cf5abc05a17ccd32c72a3615410d5365ac167a/regex-2025.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08b884f4226602ad40c5d55f52bf91a9df30f513864e0054bad40c0e9cf1afb7", size = 798568 }, + { url = "https://files.pythonhosted.org/packages/64/15/92c1db4fa4e12733dd5a526c2dd2b6edcbfe13257e135fc0f6c57f34c173/regex-2025.11.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3e0b11b2b2433d1c39c7c7a30e3f3d0aeeea44c2a8d0bae28f6b95f639927a69", size = 864165 }, + { url = "https://files.pythonhosted.org/packages/f9/e7/3ad7da8cdee1ce66c7cd37ab5ab05c463a86ffeb52b1a25fe7bd9293b36c/regex-2025.11.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87eb52a81ef58c7ba4d45c3ca74e12aa4b4e77816f72ca25258a85b3ea96cb48", size = 912182 }, + { url = "https://files.pythonhosted.org/packages/84/bd/9ce9f629fcb714ffc2c3faf62b6766ecb7a585e1e885eb699bcf130a5209/regex-2025.11.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a12ab1f5c29b4e93db518f5e3872116b7e9b1646c9f9f426f777b50d44a09e8c", size = 803501 }, + { url = "https://files.pythonhosted.org/packages/7c/0f/8dc2e4349d8e877283e6edd6c12bdcebc20f03744e86f197ab6e4492bf08/regex-2025.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7521684c8c7c4f6e88e35ec89680ee1aa8358d3f09d27dfbdf62c446f5d4c695", size = 787842 }, + { url = "https://files.pythonhosted.org/packages/f9/73/cff02702960bc185164d5619c0c62a2f598a6abff6695d391b096237d4ab/regex-2025.11.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7fe6e5440584e94cc4b3f5f4d98a25e29ca12dccf8873679a635638349831b98", size = 858519 }, + { url = "https://files.pythonhosted.org/packages/61/83/0e8d1ae71e15bc1dc36231c90b46ee35f9d52fab2e226b0e039e7ea9c10a/regex-2025.11.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8e026094aa12b43f4fd74576714e987803a315c76edb6b098b9809db5de58f74", size = 850611 }, + { url = "https://files.pythonhosted.org/packages/c8/f5/70a5cdd781dcfaa12556f2955bf170cd603cb1c96a1827479f8faea2df97/regex-2025.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:435bbad13e57eb5606a68443af62bed3556de2f46deb9f7d4237bc2f1c9fb3a0", size = 789759 }, + { url = "https://files.pythonhosted.org/packages/59/9b/7c29be7903c318488983e7d97abcf8ebd3830e4c956c4c540005fcfb0462/regex-2025.11.3-cp312-cp312-win32.whl", hash = "sha256:3839967cf4dc4b985e1570fd8d91078f0c519f30491c60f9ac42a8db039be204", size = 266194 }, + { url = "https://files.pythonhosted.org/packages/1a/67/3b92df89f179d7c367be654ab5626ae311cb28f7d5c237b6bb976cd5fbbb/regex-2025.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:e721d1b46e25c481dc5ded6f4b3f66c897c58d2e8cfdf77bbced84339108b0b9", size = 277069 }, + { url = "https://files.pythonhosted.org/packages/d7/55/85ba4c066fe5094d35b249c3ce8df0ba623cfd35afb22d6764f23a52a1c5/regex-2025.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:64350685ff08b1d3a6fff33f45a9ca183dc1d58bbfe4981604e70ec9801bbc26", size = 270330 }, + { url = "https://files.pythonhosted.org/packages/e1/a7/dda24ebd49da46a197436ad96378f17df30ceb40e52e859fc42cac45b850/regex-2025.11.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c1e448051717a334891f2b9a620fe36776ebf3dd8ec46a0b877c8ae69575feb4", size = 489081 }, + { url = "https://files.pythonhosted.org/packages/19/22/af2dc751aacf88089836aa088a1a11c4f21a04707eb1b0478e8e8fb32847/regex-2025.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b5aca4d5dfd7fbfbfbdaf44850fcc7709a01146a797536a8f84952e940cca76", size = 291123 }, + { url = "https://files.pythonhosted.org/packages/a3/88/1a3ea5672f4b0a84802ee9891b86743438e7c04eb0b8f8c4e16a42375327/regex-2025.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:04d2765516395cf7dda331a244a3282c0f5ae96075f728629287dfa6f76ba70a", size = 288814 }, + { url = "https://files.pythonhosted.org/packages/fb/8c/f5987895bf42b8ddeea1b315c9fedcfe07cadee28b9c98cf50d00adcb14d/regex-2025.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d9903ca42bfeec4cebedba8022a7c97ad2aab22e09573ce9976ba01b65e4361", size = 798592 }, + { url = "https://files.pythonhosted.org/packages/99/2a/6591ebeede78203fa77ee46a1c36649e02df9eaa77a033d1ccdf2fcd5d4e/regex-2025.11.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:639431bdc89d6429f6721625e8129413980ccd62e9d3f496be618a41d205f160", size = 864122 }, + { url = "https://files.pythonhosted.org/packages/94/d6/be32a87cf28cf8ed064ff281cfbd49aefd90242a83e4b08b5a86b38e8eb4/regex-2025.11.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f117efad42068f9715677c8523ed2be1518116d1c49b1dd17987716695181efe", size = 912272 }, + { url = "https://files.pythonhosted.org/packages/62/11/9bcef2d1445665b180ac7f230406ad80671f0fc2a6ffb93493b5dd8cd64c/regex-2025.11.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4aecb6f461316adf9f1f0f6a4a1a3d79e045f9b71ec76055a791affa3b285850", size = 803497 }, + { url = "https://files.pythonhosted.org/packages/e5/a7/da0dc273d57f560399aa16d8a68ae7f9b57679476fc7ace46501d455fe84/regex-2025.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3b3a5f320136873cc5561098dfab677eea139521cb9a9e8db98b7e64aef44cbc", size = 787892 }, + { url = "https://files.pythonhosted.org/packages/da/4b/732a0c5a9736a0b8d6d720d4945a2f1e6f38f87f48f3173559f53e8d5d82/regex-2025.11.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:75fa6f0056e7efb1f42a1c34e58be24072cb9e61a601340cc1196ae92326a4f9", size = 858462 }, + { url = "https://files.pythonhosted.org/packages/0c/f5/a2a03df27dc4c2d0c769220f5110ba8c4084b0bfa9ab0f9b4fcfa3d2b0fc/regex-2025.11.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:dbe6095001465294f13f1adcd3311e50dd84e5a71525f20a10bd16689c61ce0b", size = 850528 }, + { url = "https://files.pythonhosted.org/packages/d6/09/e1cd5bee3841c7f6eb37d95ca91cdee7100b8f88b81e41c2ef426910891a/regex-2025.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:454d9b4ae7881afbc25015b8627c16d88a597479b9dea82b8c6e7e2e07240dc7", size = 789866 }, + { url = "https://files.pythonhosted.org/packages/eb/51/702f5ea74e2a9c13d855a6a85b7f80c30f9e72a95493260193c07f3f8d74/regex-2025.11.3-cp313-cp313-win32.whl", hash = "sha256:28ba4d69171fc6e9896337d4fc63a43660002b7da53fc15ac992abcf3410917c", size = 266189 }, + { url = "https://files.pythonhosted.org/packages/8b/00/6e29bb314e271a743170e53649db0fdb8e8ff0b64b4f425f5602f4eb9014/regex-2025.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:bac4200befe50c670c405dc33af26dad5a3b6b255dd6c000d92fe4629f9ed6a5", size = 277054 }, + { url = "https://files.pythonhosted.org/packages/25/f1/b156ff9f2ec9ac441710764dda95e4edaf5f36aca48246d1eea3f1fd96ec/regex-2025.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:2292cd5a90dab247f9abe892ac584cb24f0f54680c73fcb4a7493c66c2bf2467", size = 270325 }, + { url = "https://files.pythonhosted.org/packages/20/28/fd0c63357caefe5680b8ea052131acbd7f456893b69cc2a90cc3e0dc90d4/regex-2025.11.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1eb1ebf6822b756c723e09f5186473d93236c06c579d2cc0671a722d2ab14281", size = 491984 }, + { url = "https://files.pythonhosted.org/packages/df/ec/7014c15626ab46b902b3bcc4b28a7bae46d8f281fc7ea9c95e22fcaaa917/regex-2025.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1e00ec2970aab10dc5db34af535f21fcf32b4a31d99e34963419636e2f85ae39", size = 292673 }, + { url = "https://files.pythonhosted.org/packages/23/ab/3b952ff7239f20d05f1f99e9e20188513905f218c81d52fb5e78d2bf7634/regex-2025.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a4cb042b615245d5ff9b3794f56be4138b5adc35a4166014d31d1814744148c7", size = 291029 }, + { url = "https://files.pythonhosted.org/packages/21/7e/3dc2749fc684f455f162dcafb8a187b559e2614f3826877d3844a131f37b/regex-2025.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44f264d4bf02f3176467d90b294d59bf1db9fe53c141ff772f27a8b456b2a9ed", size = 807437 }, + { url = "https://files.pythonhosted.org/packages/1b/0b/d529a85ab349c6a25d1ca783235b6e3eedf187247eab536797021f7126c6/regex-2025.11.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7be0277469bf3bd7a34a9c57c1b6a724532a0d235cd0dc4e7f4316f982c28b19", size = 873368 }, + { url = "https://files.pythonhosted.org/packages/7d/18/2d868155f8c9e3e9d8f9e10c64e9a9f496bb8f7e037a88a8bed26b435af6/regex-2025.11.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0d31e08426ff4b5b650f68839f5af51a92a5b51abd8554a60c2fbc7c71f25d0b", size = 914921 }, + { url = "https://files.pythonhosted.org/packages/2d/71/9d72ff0f354fa783fe2ba913c8734c3b433b86406117a8db4ea2bf1c7a2f/regex-2025.11.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e43586ce5bd28f9f285a6e729466841368c4a0353f6fd08d4ce4630843d3648a", size = 812708 }, + { url = "https://files.pythonhosted.org/packages/e7/19/ce4bf7f5575c97f82b6e804ffb5c4e940c62609ab2a0d9538d47a7fdf7d4/regex-2025.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0f9397d561a4c16829d4e6ff75202c1c08b68a3bdbfe29dbfcdb31c9830907c6", size = 795472 }, + { url = "https://files.pythonhosted.org/packages/03/86/fd1063a176ffb7b2315f9a1b08d17b18118b28d9df163132615b835a26ee/regex-2025.11.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:dd16e78eb18ffdb25ee33a0682d17912e8cc8a770e885aeee95020046128f1ce", size = 868341 }, + { url = "https://files.pythonhosted.org/packages/12/43/103fb2e9811205e7386366501bc866a164a0430c79dd59eac886a2822950/regex-2025.11.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:ffcca5b9efe948ba0661e9df0fa50d2bc4b097c70b9810212d6b62f05d83b2dd", size = 854666 }, + { url = "https://files.pythonhosted.org/packages/7d/22/e392e53f3869b75804762c7c848bd2dd2abf2b70fb0e526f58724638bd35/regex-2025.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c56b4d162ca2b43318ac671c65bd4d563e841a694ac70e1a976ac38fcf4ca1d2", size = 799473 }, + { url = "https://files.pythonhosted.org/packages/4f/f9/8bd6b656592f925b6845fcbb4d57603a3ac2fb2373344ffa1ed70aa6820a/regex-2025.11.3-cp313-cp313t-win32.whl", hash = "sha256:9ddc42e68114e161e51e272f667d640f97e84a2b9ef14b7477c53aac20c2d59a", size = 268792 }, + { url = "https://files.pythonhosted.org/packages/e5/87/0e7d603467775ff65cd2aeabf1b5b50cc1c3708556a8b849a2fa4dd1542b/regex-2025.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7a7c7fdf755032ffdd72c77e3d8096bdcb0eb92e89e17571a196f03d88b11b3c", size = 280214 }, + { url = "https://files.pythonhosted.org/packages/8d/d0/2afc6f8e94e2b64bfb738a7c2b6387ac1699f09f032d363ed9447fd2bb57/regex-2025.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:df9eb838c44f570283712e7cff14c16329a9f0fb19ca492d21d4b7528ee6821e", size = 271469 }, + { url = "https://files.pythonhosted.org/packages/31/e9/f6e13de7e0983837f7b6d238ad9458800a874bf37c264f7923e63409944c/regex-2025.11.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9697a52e57576c83139d7c6f213d64485d3df5bf84807c35fa409e6c970801c6", size = 489089 }, + { url = "https://files.pythonhosted.org/packages/a3/5c/261f4a262f1fa65141c1b74b255988bd2fa020cc599e53b080667d591cfc/regex-2025.11.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e18bc3f73bd41243c9b38a6d9f2366cd0e0137a9aebe2d8ff76c5b67d4c0a3f4", size = 291059 }, + { url = "https://files.pythonhosted.org/packages/8e/57/f14eeb7f072b0e9a5a090d1712741fd8f214ec193dba773cf5410108bb7d/regex-2025.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:61a08bcb0ec14ff4e0ed2044aad948d0659604f824cbd50b55e30b0ec6f09c73", size = 288900 }, + { url = "https://files.pythonhosted.org/packages/3c/6b/1d650c45e99a9b327586739d926a1cd4e94666b1bd4af90428b36af66dc7/regex-2025.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9c30003b9347c24bcc210958c5d167b9e4f9be786cb380a7d32f14f9b84674f", size = 799010 }, + { url = "https://files.pythonhosted.org/packages/99/ee/d66dcbc6b628ce4e3f7f0cbbb84603aa2fc0ffc878babc857726b8aab2e9/regex-2025.11.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4e1e592789704459900728d88d41a46fe3969b82ab62945560a31732ffc19a6d", size = 864893 }, + { url = "https://files.pythonhosted.org/packages/bf/2d/f238229f1caba7ac87a6c4153d79947fb0261415827ae0f77c304260c7d3/regex-2025.11.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6538241f45eb5a25aa575dbba1069ad786f68a4f2773a29a2bd3dd1f9de787be", size = 911522 }, + { url = "https://files.pythonhosted.org/packages/bd/3d/22a4eaba214a917c80e04f6025d26143690f0419511e0116508e24b11c9b/regex-2025.11.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce22519c989bb72a7e6b36a199384c53db7722fe669ba891da75907fe3587db", size = 803272 }, + { url = "https://files.pythonhosted.org/packages/84/b1/03188f634a409353a84b5ef49754b97dbcc0c0f6fd6c8ede505a8960a0a4/regex-2025.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:66d559b21d3640203ab9075797a55165d79017520685fb407b9234d72ab63c62", size = 787958 }, + { url = "https://files.pythonhosted.org/packages/99/6a/27d072f7fbf6fadd59c64d210305e1ff865cc3b78b526fd147db768c553b/regex-2025.11.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:669dcfb2e38f9e8c69507bace46f4889e3abbfd9b0c29719202883c0a603598f", size = 859289 }, + { url = "https://files.pythonhosted.org/packages/9a/70/1b3878f648e0b6abe023172dacb02157e685564853cc363d9961bcccde4e/regex-2025.11.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:32f74f35ff0f25a5021373ac61442edcb150731fbaa28286bbc8bb1582c89d02", size = 850026 }, + { url = "https://files.pythonhosted.org/packages/dd/d5/68e25559b526b8baab8e66839304ede68ff6727237a47727d240006bd0ff/regex-2025.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e6c7a21dffba883234baefe91bc3388e629779582038f75d2a5be918e250f0ed", size = 789499 }, + { url = "https://files.pythonhosted.org/packages/fc/df/43971264857140a350910d4e33df725e8c94dd9dee8d2e4729fa0d63d49e/regex-2025.11.3-cp314-cp314-win32.whl", hash = "sha256:795ea137b1d809eb6836b43748b12634291c0ed55ad50a7d72d21edf1cd565c4", size = 271604 }, + { url = "https://files.pythonhosted.org/packages/01/6f/9711b57dc6894a55faf80a4c1b5aa4f8649805cb9c7aef46f7d27e2b9206/regex-2025.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f95fbaa0ee1610ec0fc6b26668e9917a582ba80c52cc6d9ada15e30aa9ab9ad", size = 280320 }, + { url = "https://files.pythonhosted.org/packages/f1/7e/f6eaa207d4377481f5e1775cdeb5a443b5a59b392d0065f3417d31d80f87/regex-2025.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:dfec44d532be4c07088c3de2876130ff0fbeeacaa89a137decbbb5f665855a0f", size = 273372 }, + { url = "https://files.pythonhosted.org/packages/c3/06/49b198550ee0f5e4184271cee87ba4dfd9692c91ec55289e6282f0f86ccf/regex-2025.11.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ba0d8a5d7f04f73ee7d01d974d47c5834f8a1b0224390e4fe7c12a3a92a78ecc", size = 491985 }, + { url = "https://files.pythonhosted.org/packages/ce/bf/abdafade008f0b1c9da10d934034cb670432d6cf6cbe38bbb53a1cfd6cf8/regex-2025.11.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:442d86cf1cfe4faabf97db7d901ef58347efd004934da045c745e7b5bd57ac49", size = 292669 }, + { url = "https://files.pythonhosted.org/packages/f9/ef/0c357bb8edbd2ad8e273fcb9e1761bc37b8acbc6e1be050bebd6475f19c1/regex-2025.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fd0a5e563c756de210bb964789b5abe4f114dacae9104a47e1a649b910361536", size = 291030 }, + { url = "https://files.pythonhosted.org/packages/79/06/edbb67257596649b8fb088d6aeacbcb248ac195714b18a65e018bf4c0b50/regex-2025.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf3490bcbb985a1ae97b2ce9ad1c0f06a852d5b19dde9b07bdf25bf224248c95", size = 807674 }, + { url = "https://files.pythonhosted.org/packages/f4/d9/ad4deccfce0ea336296bd087f1a191543bb99ee1c53093dcd4c64d951d00/regex-2025.11.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3809988f0a8b8c9dcc0f92478d6501fac7200b9ec56aecf0ec21f4a2ec4b6009", size = 873451 }, + { url = "https://files.pythonhosted.org/packages/13/75/a55a4724c56ef13e3e04acaab29df26582f6978c000ac9cd6810ad1f341f/regex-2025.11.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f4ff94e58e84aedb9c9fce66d4ef9f27a190285b451420f297c9a09f2b9abee9", size = 914980 }, + { url = "https://files.pythonhosted.org/packages/67/1e/a1657ee15bd9116f70d4a530c736983eed997b361e20ecd8f5ca3759d5c5/regex-2025.11.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eb542fd347ce61e1321b0a6b945d5701528dca0cd9759c2e3bb8bd57e47964d", size = 812852 }, + { url = "https://files.pythonhosted.org/packages/b8/6f/f7516dde5506a588a561d296b2d0044839de06035bb486b326065b4c101e/regex-2025.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2d5919075a1f2e413c00b056ea0c2f065b3f5fe83c3d07d325ab92dce51d6", size = 795566 }, + { url = "https://files.pythonhosted.org/packages/d9/dd/3d10b9e170cc16fb34cb2cef91513cf3df65f440b3366030631b2984a264/regex-2025.11.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3f8bf11a4827cc7ce5a53d4ef6cddd5ad25595d3c1435ef08f76825851343154", size = 868463 }, + { url = "https://files.pythonhosted.org/packages/f5/8e/935e6beff1695aa9085ff83195daccd72acc82c81793df480f34569330de/regex-2025.11.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:22c12d837298651e5550ac1d964e4ff57c3f56965fc1812c90c9fb2028eaf267", size = 854694 }, + { url = "https://files.pythonhosted.org/packages/92/12/10650181a040978b2f5720a6a74d44f841371a3d984c2083fc1752e4acf6/regex-2025.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ba394a3dda9ad41c7c780f60f6e4a70988741415ae96f6d1bf6c239cf01379", size = 799691 }, + { url = "https://files.pythonhosted.org/packages/67/90/8f37138181c9a7690e7e4cb388debbd389342db3c7381d636d2875940752/regex-2025.11.3-cp314-cp314t-win32.whl", hash = "sha256:4bf146dca15cdd53224a1bf46d628bd7590e4a07fbb69e720d561aea43a32b38", size = 274583 }, + { url = "https://files.pythonhosted.org/packages/8f/cd/867f5ec442d56beb56f5f854f40abcfc75e11d10b11fdb1869dd39c63aaf/regex-2025.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:adad1a1bcf1c9e76346e091d22d23ac54ef28e1365117d99521631078dfec9de", size = 284286 }, + { url = "https://files.pythonhosted.org/packages/20/31/32c0c4610cbc070362bf1d2e4ea86d1ea29014d400a6d6c2486fcfd57766/regex-2025.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c54f768482cef41e219720013cd05933b6f971d9562544d691c68699bf2b6801", size = 274741 }, ] [[package]] @@ -2187,79 +2173,97 @@ wheels = [ [[package]] name = "rignore" -version = "0.7.2" +version = "0.7.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b1/b5/1fe06acc517315fba13db19039e77a2b9689451e0b5b02e03f26f05f24ec/rignore-0.7.2.tar.gz", hash = "sha256:b343749a59b53db30be1180ffab6995a914a244860e31a5cbea25bb647c38a61", size = 15254 } +sdist = { url = "https://files.pythonhosted.org/packages/e5/f5/8bed2310abe4ae04b67a38374a4d311dd85220f5d8da56f47ae9361be0b0/rignore-0.7.6.tar.gz", hash = "sha256:00d3546cd793c30cb17921ce674d2c8f3a4b00501cb0e3dd0e82217dbeba2671", size = 57140 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1e/34f11b4ebc331fc8f137d2b65304667a58bd2b321ce6309ac1e6f7f1c9b2/rignore-0.7.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ed6ec2d50664865feea344b2e39eaad697f0f2b1676a26add1b458e416120a2b", size = 891771 }, - { url = "https://files.pythonhosted.org/packages/d0/a8/4240d08eb693908451bcb6efc27e1ae936dee8b1adfd5fbcc7f7668fb961/rignore-0.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9bad1790c32bf1f84bed6f2750933cfe67be056da074ed98a8808f7fb6d0aae0", size = 823881 }, - { url = "https://files.pythonhosted.org/packages/19/a7/162a821b67e3ef0444c8713ae28c6a66f3ffed29197b3927ae5513020591/rignore-0.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ac56757af3b224ffb20368f033007404676d4e211d85a0b95b6b57cd94898ec", size = 901542 }, - { url = "https://files.pythonhosted.org/packages/3c/b3/6d5ae8b7b2fb94f5a3962e281843c983c8ed0a57bc37ac8ef893f581e460/rignore-0.7.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11284ae105e5e80539a420b194d8624940f7836caafb9cde45e2b590ed957ebf", size = 874673 }, - { url = "https://files.pythonhosted.org/packages/4c/89/83063fb4d4b57d00cb9b6a04878e5971830518c001bb44ad1b93ace4f476/rignore-0.7.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5643cf2857a80744bd43752fac245de5f65effaafb0bac2736d62ded39b82d8", size = 1177702 }, - { url = "https://files.pythonhosted.org/packages/e9/53/6ac52ead4dbc99acb5a1de1794ef522f93dacf535d017469d64ff84f4262/rignore-0.7.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:193e6414107634546416fa80e0ca67c5e5899eb5fcd699f444138b8f25d557d1", size = 944091 }, - { url = "https://files.pythonhosted.org/packages/ec/7b/fcf87d8050f103e377357bd599d0d976d67623c1a1f87d454e9a97a7e605/rignore-0.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:059932ba1bf00130bce0ee8569e44b466fd4e249e3befffdca50843e0e63d7b5", size = 959575 }, - { url = "https://files.pythonhosted.org/packages/73/fa/d3aa50f33376c6ec44ba96af6d576aa5d5fb16087e39fb2646eca789ef05/rignore-0.7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0de4bcf93471999260885c123ebac59ddab96257649b3a0cb7daefbfa7ed7714", size = 985688 }, - { url = "https://files.pythonhosted.org/packages/b2/58/761abdf261b4aefa0e41a5d52093fbc7e6a53ce52fdb6dc5403c9ad43558/rignore-0.7.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9049a42a9c34ee4f191038f653e14eb8bdaf3e913c7ebfc7cb0945896ab8cd2a", size = 1082297 }, - { url = "https://files.pythonhosted.org/packages/d2/01/d9a396d7535f3b0fce0f9e0f6317e498a70d801831dabf2c3648835e9d95/rignore-0.7.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f11e61dcaf60273661f233792a6fb144a998a43dd92f9f007e3fda57fbc2178a", size = 1138750 }, - { url = "https://files.pythonhosted.org/packages/a0/8b/821a446169d72280295b76e946d34e4a58aa2ae75e81e317816c9146071e/rignore-0.7.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:844af954b26af2ace2c333b78691acb939e55b3aa7ad5ca0c4cd27085a1088a2", size = 1118562 }, - { url = "https://files.pythonhosted.org/packages/b7/97/68ddb8f52efde41e0079e494eda0ac0168993e9a1a85470d2510575d566f/rignore-0.7.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:999d746d2c345f050b063be57481d3658a4bb561d27685db866c8ebb218d6199", size = 1125516 }, - { url = "https://files.pythonhosted.org/packages/6b/0e/d0de1198a246bd9418945d415f4cf66a4a325ecc5fa6832d3c8648bdf04c/rignore-0.7.2-cp311-cp311-win32.whl", hash = "sha256:b63c880029b3bfb7cfacc43312bae1b73763078ac29f249074fb86e60d2206ac", size = 646549 }, - { url = "https://files.pythonhosted.org/packages/ce/56/c3635d04c29edf7f56ff6b1e30c9d05c4b8a5982f3adea4fc87efe3af260/rignore-0.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:c8c1b971689fbd4cdb88ab63626c45ae22264f8927ab12013764fb8e242b3908", size = 727143 }, - { url = "https://files.pythonhosted.org/packages/19/63/7f16c23743cd7b3a31cf7f02ce9e8b8127873f75250cbdd451e87f1eab4e/rignore-0.7.2-cp311-cp311-win_arm64.whl", hash = "sha256:7de5ce5fd19009656712d70f9b9ab0eeeed2ca126c7080ae7cee87a9256803c0", size = 657622 }, - { url = "https://files.pythonhosted.org/packages/a2/aa/6f21e66910ec1745dec1d1b0ffb97977bfcf76b520c60079c0fca050c702/rignore-0.7.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2220958d76332fa16aff92b71754ba10601cb2ba66723f87fb64e09d7a8d121a", size = 889758 }, - { url = "https://files.pythonhosted.org/packages/c1/07/20fd5d14677bb34d6ee93f331f4b6bbb593806f8ebda3c0464074b3e8b20/rignore-0.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4dd674966c8219b82ea165e3717d629d5a08aad80697489a1bf750c8517f3b9", size = 820405 }, - { url = "https://files.pythonhosted.org/packages/a4/8b/412ab22fba7eeabf7cbd6d5098ee00989dcfcae8b68a8bf3f5c0c21812a0/rignore-0.7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25323d7d93fa4d93fb2149dbedde911907de933a573b6c475e2d2b248384b42b", size = 901575 }, - { url = "https://files.pythonhosted.org/packages/32/3e/acc817bc5267ffc42ae65f80f12cfa804db6be6ba58c8f3af06a60f5ced7/rignore-0.7.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b429d97fbe3e2c8180dd10684668d2da624a2f9b2e8cb625dc12cb3c77d1f22", size = 874033 }, - { url = "https://files.pythonhosted.org/packages/05/f8/709846896df0ca1119409339fe9292bdf8f8b142eaee90fc52468844adc5/rignore-0.7.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbcac80502ca335f38f6bbbacb9b4818622649235d028b0eb01a931b77a38dc2", size = 1176041 }, - { url = "https://files.pythonhosted.org/packages/a4/2f/e4a92d18e4cfcf6f83b82d2928a8f8c5d3a6c8306f4791d89c88cdd80a5f/rignore-0.7.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35a8a220e7a38f672da8ec58126e52ffabfc0d5f833e2047e89f2fa0c2c0cc6a", size = 944453 }, - { url = "https://files.pythonhosted.org/packages/7a/12/ca2d6fc7b68c916fe6d68fc63230f4b60be7a5fc8cebc62962ab342b60c7/rignore-0.7.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e541c1d4717ce5e938748c5302b6ebe63f4eed08bfca68a1861a214a9c8316f2", size = 959266 }, - { url = "https://files.pythonhosted.org/packages/b4/68/97693bbc3fdd65401f44377097040d285f42e6013ba5eb2da8b97fcf2dd4/rignore-0.7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a35db5bb7ab3f9a37e131a97bc3388ad3af0fc8a90b9d91799f33afde2a8e15", size = 985338 }, - { url = "https://files.pythonhosted.org/packages/e0/95/b580fdb7666ffe17c52258e48b8d1217be53eaf69f7ad5c2ecc680782836/rignore-0.7.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9cee5d75c8b1bc1450855dde82c6e3ac2d258238410eba710e2326c2e2cf4cce", size = 1081155 }, - { url = "https://files.pythonhosted.org/packages/a1/d7/08d067e0d0011bf18c32833b3f82a119bc191625fb4156db589bb5f3d826/rignore-0.7.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fd1062ecc16bb8cc32d7ad2e13c04fc3db74919ed8c4d79a9e55b32d50b40c0f", size = 1137958 }, - { url = "https://files.pythonhosted.org/packages/c0/d1/6f3b624e671d2bc24c2a8450d30036a4f387bdae2b556604e0eb67ba3975/rignore-0.7.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e03c758f81f4084d37cd6095c837f24f4fcdc4238d2a921ae880cd0f21c02850", size = 1117831 }, - { url = "https://files.pythonhosted.org/packages/9d/e5/ef8eed0a0f4bab93f470a58430cea4e4b69bb6864bdcb9237276f08a133c/rignore-0.7.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8d27901bf65db37778990db08cca809cbd4e0be0ea08aef6eb850e4a624b1c1", size = 1125163 }, - { url = "https://files.pythonhosted.org/packages/44/18/11f4af8d56e1941bb8c32749fb15f43b11a855ac4ea0091796c5b289abdf/rignore-0.7.2-cp312-cp312-win32.whl", hash = "sha256:85b511bcd85cc521bfda40ca60a08e35d82c2f4d87a33100f5308cc4e150708e", size = 646153 }, - { url = "https://files.pythonhosted.org/packages/a0/26/b1963edafa3ce974e70ca3fe914ea965c631e21a6cb63de7a87ba78deb89/rignore-0.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:bffd6b885c450ca6d7ab3bb9ff6b1d910a74aec45bb427580c2a91089d2127cb", size = 726157 }, - { url = "https://files.pythonhosted.org/packages/ef/d4/1119b58862bb6e7918c28dd90511f5743a462590eced505caa71f3c7384a/rignore-0.7.2-cp312-cp312-win_arm64.whl", hash = "sha256:036bb1597fab0ebc5082abfaf6b13eaced7769703a492c5772844edc34bc8f76", size = 656322 }, - { url = "https://files.pythonhosted.org/packages/e5/ce/c77d73a611a47b021b1536f7b49fe5593fec0b5e43934166e0c1fcfd1d4c/rignore-0.7.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2b98b8396f856f302e983664b1e2a13aee8c82d8ce0b34c2548a0e09a7c30d3c", size = 889368 }, - { url = "https://files.pythonhosted.org/packages/11/dc/bbbe0d23051605cd2197626d3a5212f376d5e0143881cdbf6632c8ecb38b/rignore-0.7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bfdfb906ad0f8f22d2685fb2b6465a078d78ee32e437dab4ab35080a2790c87b", size = 820141 }, - { url = "https://files.pythonhosted.org/packages/5d/62/ee54bc98dc986de7bf8cfddbb62670cbcbbfc21b4c53821421be96d561d0/rignore-0.7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3eef7c19742af7d3d813917a81af65ed9d7050f49f90fd78986a0243170531a", size = 901513 }, - { url = "https://files.pythonhosted.org/packages/a0/e5/e87a724794d23e1aaf7f9a5b2108fefb64703784e88f1082df36631c424a/rignore-0.7.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a73c14e1a131b17235fac9b148d549e6bd90abb4e9950baeb2df1e09e467bf6d", size = 873815 }, - { url = "https://files.pythonhosted.org/packages/07/02/7a804c2491d9794aef7052a4cdb6343ff6fdee5d68adc6e859f4f46363e8/rignore-0.7.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2927a547bd6934882fc92f55d77b8c4d548655612db502b509e495cbe9ef39eb", size = 1177286 }, - { url = "https://files.pythonhosted.org/packages/4a/6b/0b84972c4442b60d6afb450607708aa74e2b416f403e12541c65a3e49c50/rignore-0.7.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fce3b899a3a891744264edde217a8d3a9fc4e9f542afe1c4b88bfa8544509cca", size = 944310 }, - { url = "https://files.pythonhosted.org/packages/c0/35/abb0816263aaaee399730a701636c81090455203af67601cc409adb8d431/rignore-0.7.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ea5364e7e0a188ee794be4335eaad1df089b8226279b460c98d8b95c11b73d", size = 958713 }, - { url = "https://files.pythonhosted.org/packages/14/70/0573d0bcf3fb27b3960c601027db9e31338c56e3a899e6d1c649b872bb62/rignore-0.7.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a118788ce31693c02629851b4431043d5949c506e15f45d7ccd0cdc3d3e65765", size = 985183 }, - { url = "https://files.pythonhosted.org/packages/72/03/f25ff93e3ede74e8c7908c675ba643ec67fb4fee48a4d8bcc2c2880c53b5/rignore-0.7.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:80c978468562464be9c64de6e086707103a727fec0ec88961d88bca91111f1a9", size = 1080365 }, - { url = "https://files.pythonhosted.org/packages/fd/0c/9a273bf389e0651f118e35f2f4acbe2ed0ceecb570f1ea49475e59d8149e/rignore-0.7.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ea0a073a7b9639be68d8269732630d1ddf55fb72f5e4faa0e1b3d2f46d9e6b48", size = 1137639 }, - { url = "https://files.pythonhosted.org/packages/c9/d5/009ce164e2ef31bc0cf5506337cd5eca495c7b5ea526cb4ccbbbfe8b9928/rignore-0.7.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a15dfd14b5d9118e1e4afbc5e951b1a5ce43185d1605aac5f46ad72a6c53952a", size = 1117566 }, - { url = "https://files.pythonhosted.org/packages/a9/3a/c2aed0787572cc0a0c5afcafb9bbd8827fb676fe89ca3a78cdf62e656f14/rignore-0.7.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bde72ba6474cea23ca9091a66959caaaa915091e472fff95ced1b341d7541300", size = 1124968 }, - { url = "https://files.pythonhosted.org/packages/63/fa/4ab82713918e6a8fc1ef9a609a19baeeb9ddc37e7ba10620045f10689c56/rignore-0.7.2-cp313-cp313-win32.whl", hash = "sha256:011c6ede35ad03c4f84c72c6535033f31c56543830222586e9ef09274b22688a", size = 646108 }, - { url = "https://files.pythonhosted.org/packages/ea/46/c91aac0466158973c8c9deb00ab2bbb870dabc726261dd786246bb62201c/rignore-0.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:26fb0c20f77e24b9dd361cce8c78c7b581fbceab8b2a06e4374c54f5ce63c475", size = 726268 }, - { url = "https://files.pythonhosted.org/packages/3e/41/815c603dff6512ec35ff7ff2b5d8a10f0884203eb71e8d22d5ce3c49bc71/rignore-0.7.2-cp313-cp313-win_arm64.whl", hash = "sha256:4d7d33e36a4f53f1765d3340e126758a1cf232cba9f27d2458f806dad434793e", size = 656198 }, - { url = "https://files.pythonhosted.org/packages/f3/53/b26ad855d846b5426eeb8da22fc47753312b054583cad3a78cbf7375e3e6/rignore-0.7.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d4997bc54ca11f13013e05c271770e1ec20195e4fe21276ea6b91f5c5dced25", size = 820745 }, - { url = "https://files.pythonhosted.org/packages/6f/20/7ebc5949807fb89683d7f3c512d3161d0eb8c01183d0acb569a8f2721eec/rignore-0.7.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5923f3d5481cdd683540ff70c1e9ad1bd369823578e2d49987aedd1c3dedb5", size = 901796 }, - { url = "https://files.pythonhosted.org/packages/be/85/d9166578342e0ef284baece0e843546c1cb4db397d995798a1ec797e502f/rignore-0.7.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1cd0a4c1babd64dda268d6a7a5efa998c717e2af0a49717f5f8e9524c92f2595", size = 874141 }, - { url = "https://files.pythonhosted.org/packages/a3/59/83d233b9b787c876d9a2b24efd69a5ad5729f6bb01e0ec753a7e09372ff0/rignore-0.7.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e20381b7487479bb75544e6e96141fe20873a8c78c8ed36ceb2ffdbdf9dbfcba", size = 1176316 }, - { url = "https://files.pythonhosted.org/packages/c1/49/852aeab984b7919083e47fe572bcd796bc30653da55b994c1aa2c7b64b8a/rignore-0.7.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c82a1f9b5fc264b9286cd2febc8a2e20eaf70e60b436d17393a329e24a8dbae", size = 944566 }, - { url = "https://files.pythonhosted.org/packages/1a/7c/5ae025765f3c66812fc01cdaa4f6ecd809b7f8fa92a39600865d5d9dc538/rignore-0.7.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ef66cd608f5cff2606c4fae81ac6149995c1bb3a7cd442a81c9bc2ee21774c1", size = 958463 }, - { url = "https://files.pythonhosted.org/packages/55/c1/5314352af5633b6d45d910b0fe3b2c5c7473d81a735585fc717f5198e61f/rignore-0.7.2-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbc864367d79dcdbfd92c463401b637d8760ac8619a8a31210826dd151ff30be", size = 985201 }, - { url = "https://files.pythonhosted.org/packages/1b/a4/4a300a9fb6b2d3a35845c7f51a90ca302b749fcc547e67245232e4c38f98/rignore-0.7.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:51299dcce9edb8a4fafe766ba5f90c02b51a72d2127351cdd62b252fd39e874f", size = 1081867 }, - { url = "https://files.pythonhosted.org/packages/f0/3c/8b074c9f6471588dc898a9d094d35518cb66a3942faeffdae352b2519d1f/rignore-0.7.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9fad9574e1f71f299468d558aa59350600688b05f7ec1d31a01e831ba306d9dd", size = 1138062 }, - { url = "https://files.pythonhosted.org/packages/57/72/d8e0da03c54b282e5fd8f9faf467264d06591c0fff653d243b33aa237e61/rignore-0.7.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5e12d6c3f420c5362f7ffebca815db298ed0976a98b2bc3e48389bc0a73ffc24", size = 1117732 }, - { url = "https://files.pythonhosted.org/packages/dc/a6/81ce73ccbddfee92d7a1ca655fe9a8f98af19ad4d82283cadb9868e40681/rignore-0.7.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0bb07648a03b7825d70d6ee92e03d7d2220bf9e1eb70a0d93cfddf64b78ce27f", size = 1125026 }, - { url = "https://files.pythonhosted.org/packages/68/d6/85af267bd20130ca58da7ec8d386cccba5a333918a375cca72dc9fb4f3b1/rignore-0.7.2-cp314-cp314-win32.whl", hash = "sha256:95b9a6bc3e83dc42359b276fa795cab81ea33a68662a47b6e7fd201d45187bf7", size = 646386 }, - { url = "https://files.pythonhosted.org/packages/75/25/d85777d2e31d7c42e2d581019b65fd6accfc3645797e011d8a8db3303445/rignore-0.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:afbe88be82ca65debf6f7bc1a9711c4d65dad4156499ded3dfd4e6a7af5f4c78", size = 725700 }, - { url = "https://files.pythonhosted.org/packages/7d/89/e8832494602b2b1f867ca7bf5901a5598fcfc2128510fcef878989cd963f/rignore-0.7.2-cp314-cp314-win_arm64.whl", hash = "sha256:e5429df475e9a17e163352df67c05026e8505da262159c7b9bfa707708bc7b93", size = 656032 }, - { url = "https://files.pythonhosted.org/packages/b5/2c/01237e1ad4fdc6bf370cd193730347f83573bf2094d2a99d58e122865c38/rignore-0.7.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57d65850b7d762a44e4a9b54379fc763042cb0cfbe42ce0d8735ca8bad64e92c", size = 902098 }, - { url = "https://files.pythonhosted.org/packages/37/e4/67fdd0fc28a7c77a0906814faa6e7f8de929897c722adb77944f7cd6338d/rignore-0.7.2-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0553d732ca6413e6c1949d0b793fea807cd5decc38163ec002e7f9faea98f279", size = 874886 }, - { url = "https://files.pythonhosted.org/packages/07/0d/740b9ee613e852728ae251eeecb92c7f40e8e17896e75f373ff66f764c83/rignore-0.7.2-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:760421831764eaaa7d278e10808a01f83da21414750f13084a9715f84050b3b6", size = 1176754 }, - { url = "https://files.pythonhosted.org/packages/6e/9a/bf6891dd85a860a7b9d5f5241db56e1c00a50625996f7638563a5efc2f62/rignore-0.7.2-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3634010a2e9f1307ed7ea6b30d2f5f6e620bb09d4ec4dc44761fa9b9d7d7ec54", size = 945090 }, - { url = "https://files.pythonhosted.org/packages/81/6b/3d2be74922963b932a2cced42ecb1876d717390c02a6b155e73bab9e878a/rignore-0.7.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:399224e352c573946e0d1eb2dc11191a2a00220576fa41493e00da99485b969a", size = 959835 }, - { url = "https://files.pythonhosted.org/packages/24/23/072b6d8c7a7c2479ae2bb8a51b8dba487821baa6d4a82e092e8d7b9b503c/rignore-0.7.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e2776241440dd5e92ea1cae6913d23d18f59e7428676d73534b8a29a21f4ca6a", size = 986706 }, - { url = "https://files.pythonhosted.org/packages/65/dd/93804cb0d26b04bffe7474d4c01f63e6b735d67998dbf9a6e8868666d984/rignore-0.7.2-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:b84e15b1951314592b56421b78eac6324cc870645fdf15fc69f3ab6ae9bd4d71", size = 1082126 }, - { url = "https://files.pythonhosted.org/packages/13/7e/9f0c611b5c5e7c6902b07e3cf98032196c179126fc083d0e166fd8b043a1/rignore-0.7.2-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:a9224806d74f9bce116d622d777b1a052b6e3a60458724f3e66a18a19e764e90", size = 1139089 }, - { url = "https://files.pythonhosted.org/packages/b1/f9/00e1c439804f288510c1ef6171afecd3fdd06d736edd8c5ee4b105d694ad/rignore-0.7.2-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:e8a6a19d11659ea29f8ac16bea2df4826c9c35bdd0d075182123934e45647903", size = 1119255 }, - { url = "https://files.pythonhosted.org/packages/67/2f/e17c92f7b6a38475a966fa684acda8617c50d21c9dc63297a54c568dcd14/rignore-0.7.2-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:68a25d918f5aab4f0af8530e171e5179252d4506bfc89785e5bdc67d8230d0a5", size = 1125901 }, + { url = "https://files.pythonhosted.org/packages/25/41/b6e2be3069ef3b7f24e35d2911bd6deb83d20ed5642ad81d5a6d1c015473/rignore-0.7.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:40be8226e12d6653abbebaffaea2885f80374c1c8f76fe5ca9e0cadd120a272c", size = 885285 }, + { url = "https://files.pythonhosted.org/packages/52/66/ba7f561b6062402022887706a7f2b2c2e2e2a28f1e3839202b0a2f77e36d/rignore-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182f4e5e4064d947c756819446a7d4cdede8e756b8c81cf9e509683fe38778d7", size = 823882 }, + { url = "https://files.pythonhosted.org/packages/f5/81/4087453df35a90b07370647b19017029324950c1b9137d54bf1f33843f17/rignore-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16b63047648a916a87be1e51bb5c009063f1b8b6f5afe4f04f875525507e63dc", size = 899362 }, + { url = "https://files.pythonhosted.org/packages/fb/c9/390a8fdfabb76d71416be773bd9f162977bd483084f68daf19da1dec88a6/rignore-0.7.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba5524f5178deca4d7695e936604ebc742acb8958f9395776e1fcb8133f8257a", size = 873633 }, + { url = "https://files.pythonhosted.org/packages/df/c9/79404fcb0faa76edfbc9df0901f8ef18568d1104919ebbbad6d608c888d1/rignore-0.7.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62020dbb89a1dd4b84ab3d60547b3b2eb2723641d5fb198463643f71eaaed57d", size = 1167633 }, + { url = "https://files.pythonhosted.org/packages/6e/8d/b3466d32d445d158a0aceb80919085baaae495b1f540fb942f91d93b5e5b/rignore-0.7.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b34acd532769d5a6f153a52a98dcb81615c949ab11697ce26b2eb776af2e174d", size = 941434 }, + { url = "https://files.pythonhosted.org/packages/e8/40/9cd949761a7af5bc27022a939c91ff622d29c7a0b66d0c13a863097dde2d/rignore-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c5e53b752f9de44dff7b3be3c98455ce3bf88e69d6dc0cf4f213346c5e3416c", size = 959461 }, + { url = "https://files.pythonhosted.org/packages/b5/87/1e1a145731f73bdb7835e11f80da06f79a00d68b370d9a847de979575e6d/rignore-0.7.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25b3536d13a5d6409ce85f23936f044576eeebf7b6db1d078051b288410fc049", size = 985323 }, + { url = "https://files.pythonhosted.org/packages/6c/31/1ecff992fc3f59c4fcdcb6c07d5f6c1e6dfb55ccda19c083aca9d86fa1c6/rignore-0.7.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e01cad2b0b92f6b1993f29fc01f23f2d78caf4bf93b11096d28e9d578eb08ce", size = 1079173 }, + { url = "https://files.pythonhosted.org/packages/17/18/162eedadb4c2282fa4c521700dbf93c9b14b8842e8354f7d72b445b8d593/rignore-0.7.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5991e46ab9b4868334c9e372ab0892b0150f3f586ff2b1e314272caeb38aaedb", size = 1139012 }, + { url = "https://files.pythonhosted.org/packages/78/96/a9ca398a8af74bb143ad66c2a31303c894111977e28b0d0eab03867f1b43/rignore-0.7.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6c8ae562e5d1246cba5eaeb92a47b2a279e7637102828dde41dcbe291f529a3e", size = 1118827 }, + { url = "https://files.pythonhosted.org/packages/9f/22/1c1a65047df864def9a047dbb40bc0b580b8289a4280e62779cd61ae21f2/rignore-0.7.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aaf938530dcc0b47c4cfa52807aa2e5bfd5ca6d57a621125fe293098692f6345", size = 1128182 }, + { url = "https://files.pythonhosted.org/packages/bd/f4/1526eb01fdc2235aca1fd9d0189bee4021d009a8dcb0161540238c24166e/rignore-0.7.6-cp311-cp311-win32.whl", hash = "sha256:166ebce373105dd485ec213a6a2695986346e60c94ff3d84eb532a237b24a4d5", size = 646547 }, + { url = "https://files.pythonhosted.org/packages/7c/c8/dda0983e1845706beb5826459781549a840fe5a7eb934abc523e8cd17814/rignore-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:44f35ee844b1a8cea50d056e6a595190ce9d42d3cccf9f19d280ae5f3058973a", size = 727139 }, + { url = "https://files.pythonhosted.org/packages/e3/47/eb1206b7bf65970d41190b879e1723fc6bbdb2d45e53565f28991a8d9d96/rignore-0.7.6-cp311-cp311-win_arm64.whl", hash = "sha256:14b58f3da4fa3d5c3fa865cab49821675371f5e979281c683e131ae29159a581", size = 657598 }, + { url = "https://files.pythonhosted.org/packages/0b/0e/012556ef3047a2628842b44e753bb15f4dc46806780ff090f1e8fe4bf1eb/rignore-0.7.6-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:03e82348cb7234f8d9b2834f854400ddbbd04c0f8f35495119e66adbd37827a8", size = 883488 }, + { url = "https://files.pythonhosted.org/packages/93/b0/d4f1f3fe9eb3f8e382d45ce5b0547ea01c4b7e0b4b4eb87bcd66a1d2b888/rignore-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9e624f6be6116ea682e76c5feb71ea91255c67c86cb75befe774365b2931961", size = 820411 }, + { url = "https://files.pythonhosted.org/packages/4a/c8/dea564b36dedac8de21c18e1851789545bc52a0c22ece9843444d5608a6a/rignore-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bda49950d405aa8d0ebe26af807c4e662dd281d926530f03f29690a2e07d649a", size = 897821 }, + { url = "https://files.pythonhosted.org/packages/b3/2b/ee96db17ac1835e024c5d0742eefb7e46de60020385ac883dd3d1cde2c1f/rignore-0.7.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5fd5ab3840b8c16851d327ed06e9b8be6459702a53e5ab1fc4073b684b3789e", size = 873963 }, + { url = "https://files.pythonhosted.org/packages/a5/8c/ad5a57bbb9d14d5c7e5960f712a8a0b902472ea3f4a2138cbf70d1777b75/rignore-0.7.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ced2a248352636a5c77504cb755dc02c2eef9a820a44d3f33061ce1bb8a7f2d2", size = 1169216 }, + { url = "https://files.pythonhosted.org/packages/80/e6/5b00bc2a6bc1701e6878fca798cf5d9125eb3113193e33078b6fc0d99123/rignore-0.7.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04a3b73b75ddc12c9c9b21efcdaab33ca3832941d6f1d67bffd860941cd448a", size = 942942 }, + { url = "https://files.pythonhosted.org/packages/85/e5/7f99bd0cc9818a91d0e8b9acc65b792e35750e3bdccd15a7ee75e64efca4/rignore-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24321efac92140b7ec910ac7c53ab0f0c86a41133d2bb4b0e6a7c94967f44dd", size = 959787 }, + { url = "https://files.pythonhosted.org/packages/55/54/2ffea79a7c1eabcede1926347ebc2a81bc6b81f447d05b52af9af14948b9/rignore-0.7.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c7aa109d41e593785c55fdaa89ad80b10330affa9f9d3e3a51fa695f739b20", size = 984245 }, + { url = "https://files.pythonhosted.org/packages/41/f7/e80f55dfe0f35787fa482aa18689b9c8251e045076c35477deb0007b3277/rignore-0.7.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1734dc49d1e9501b07852ef44421f84d9f378da9fbeda729e77db71f49cac28b", size = 1078647 }, + { url = "https://files.pythonhosted.org/packages/d4/cf/2c64f0b6725149f7c6e7e5a909d14354889b4beaadddaa5fff023ec71084/rignore-0.7.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5719ea14ea2b652c0c0894be5dfde954e1853a80dea27dd2fbaa749618d837f5", size = 1139186 }, + { url = "https://files.pythonhosted.org/packages/75/95/a86c84909ccc24af0d094b50d54697951e576c252a4d9f21b47b52af9598/rignore-0.7.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8e23424fc7ce35726854f639cb7968151a792c0c3d9d082f7f67e0c362cfecca", size = 1117604 }, + { url = "https://files.pythonhosted.org/packages/7f/5e/13b249613fd5d18d58662490ab910a9f0be758981d1797789913adb4e918/rignore-0.7.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3efdcf1dd84d45f3e2bd2f93303d9be103888f56dfa7c3349b5bf4f0657ec696", size = 1127725 }, + { url = "https://files.pythonhosted.org/packages/c7/28/fa5dcd1e2e16982c359128664e3785f202d3eca9b22dd0b2f91c4b3d242f/rignore-0.7.6-cp312-cp312-win32.whl", hash = "sha256:ccca9d1a8b5234c76b71546fc3c134533b013f40495f394a65614a81f7387046", size = 646145 }, + { url = "https://files.pythonhosted.org/packages/26/87/69387fb5dd81a0f771936381431780b8cf66fcd2cfe9495e1aaf41548931/rignore-0.7.6-cp312-cp312-win_amd64.whl", hash = "sha256:c96a285e4a8bfec0652e0bfcf42b1aabcdda1e7625f5006d188e3b1c87fdb543", size = 726090 }, + { url = "https://files.pythonhosted.org/packages/24/5f/e8418108dcda8087fb198a6f81caadbcda9fd115d61154bf0df4d6d3619b/rignore-0.7.6-cp312-cp312-win_arm64.whl", hash = "sha256:a64a750e7a8277a323f01ca50b7784a764845f6cce2fe38831cb93f0508d0051", size = 656317 }, + { url = "https://files.pythonhosted.org/packages/b7/8a/a4078f6e14932ac7edb171149c481de29969d96ddee3ece5dc4c26f9e0c3/rignore-0.7.6-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2bdab1d31ec9b4fb1331980ee49ea051c0d7f7bb6baa28b3125ef03cdc48fdaf", size = 883057 }, + { url = "https://files.pythonhosted.org/packages/f9/8f/f8daacd177db4bf7c2223bab41e630c52711f8af9ed279be2058d2fe4982/rignore-0.7.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:90f0a00ce0c866c275bf888271f1dc0d2140f29b82fcf33cdbda1e1a6af01010", size = 820150 }, + { url = "https://files.pythonhosted.org/packages/36/31/b65b837e39c3f7064c426754714ac633b66b8c2290978af9d7f513e14aa9/rignore-0.7.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ad295537041dc2ed4b540fb1a3906bd9ede6ccdad3fe79770cd89e04e3c73c", size = 897406 }, + { url = "https://files.pythonhosted.org/packages/ca/58/1970ce006c427e202ac7c081435719a076c478f07b3a23f469227788dc23/rignore-0.7.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f782dbd3a65a5ac85adfff69e5c6b101285ef3f845c3a3cae56a54bebf9fe116", size = 874050 }, + { url = "https://files.pythonhosted.org/packages/d4/00/eb45db9f90137329072a732273be0d383cb7d7f50ddc8e0bceea34c1dfdf/rignore-0.7.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65cece3b36e5b0826d946494734c0e6aaf5a0337e18ff55b071438efe13d559e", size = 1167835 }, + { url = "https://files.pythonhosted.org/packages/f3/f1/6f1d72ddca41a64eed569680587a1236633587cc9f78136477ae69e2c88a/rignore-0.7.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e4bb66c13cd7602dc8931822c02dfbbd5252015c750ac5d6152b186f0a8be0", size = 941945 }, + { url = "https://files.pythonhosted.org/packages/48/6f/2f178af1c1a276a065f563ec1e11e7a9e23d4996fd0465516afce4b5c636/rignore-0.7.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297e500c15766e196f68aaaa70e8b6db85fa23fdc075b880d8231fdfba738cd7", size = 959067 }, + { url = "https://files.pythonhosted.org/packages/5b/db/423a81c4c1e173877c7f9b5767dcaf1ab50484a94f60a0b2ed78be3fa765/rignore-0.7.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a07084211a8d35e1a5b1d32b9661a5ed20669970b369df0cf77da3adea3405de", size = 984438 }, + { url = "https://files.pythonhosted.org/packages/31/eb/c4f92cc3f2825d501d3c46a244a671eb737fc1bcf7b05a3ecd34abb3e0d7/rignore-0.7.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:181eb2a975a22256a1441a9d2f15eb1292839ea3f05606620bd9e1938302cf79", size = 1078365 }, + { url = "https://files.pythonhosted.org/packages/26/09/99442f02794bd7441bfc8ed1c7319e890449b816a7493b2db0e30af39095/rignore-0.7.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7bbcdc52b5bf9f054b34ce4af5269df5d863d9c2456243338bc193c28022bd7b", size = 1139066 }, + { url = "https://files.pythonhosted.org/packages/2c/88/bcfc21e520bba975410e9419450f4b90a2ac8236b9a80fd8130e87d098af/rignore-0.7.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f2e027a6da21a7c8c0d87553c24ca5cc4364def18d146057862c23a96546238e", size = 1118036 }, + { url = "https://files.pythonhosted.org/packages/e2/25/d37215e4562cda5c13312636393aea0bafe38d54d4e0517520a4cc0753ec/rignore-0.7.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee4a18b82cbbc648e4aac1510066682fe62beb5dc88e2c67c53a83954e541360", size = 1127550 }, + { url = "https://files.pythonhosted.org/packages/dc/76/a264ab38bfa1620ec12a8ff1c07778da89e16d8c0f3450b0333020d3d6dc/rignore-0.7.6-cp313-cp313-win32.whl", hash = "sha256:a7d7148b6e5e95035d4390396895adc384d37ff4e06781a36fe573bba7c283e5", size = 646097 }, + { url = "https://files.pythonhosted.org/packages/62/44/3c31b8983c29ea8832b6082ddb1d07b90379c2d993bd20fce4487b71b4f4/rignore-0.7.6-cp313-cp313-win_amd64.whl", hash = "sha256:b037c4b15a64dced08fc12310ee844ec2284c4c5c1ca77bc37d0a04f7bff386e", size = 726170 }, + { url = "https://files.pythonhosted.org/packages/aa/41/e26a075cab83debe41a42661262f606166157df84e0e02e2d904d134c0d8/rignore-0.7.6-cp313-cp313-win_arm64.whl", hash = "sha256:e47443de9b12fe569889bdbe020abe0e0b667516ee2ab435443f6d0869bd2804", size = 656184 }, + { url = "https://files.pythonhosted.org/packages/9a/b9/1f5bd82b87e5550cd843ceb3768b4a8ef274eb63f29333cf2f29644b3d75/rignore-0.7.6-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:8e41be9fa8f2f47239ded8920cc283699a052ac4c371f77f5ac017ebeed75732", size = 882632 }, + { url = "https://files.pythonhosted.org/packages/e9/6b/07714a3efe4a8048864e8a5b7db311ba51b921e15268b17defaebf56d3db/rignore-0.7.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6dc1e171e52cefa6c20e60c05394a71165663b48bca6c7666dee4f778f2a7d90", size = 820760 }, + { url = "https://files.pythonhosted.org/packages/ac/0f/348c829ea2d8d596e856371b14b9092f8a5dfbb62674ec9b3f67e4939a9d/rignore-0.7.6-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ce2268837c3600f82ab8db58f5834009dc638ee17103582960da668963bebc5", size = 899044 }, + { url = "https://files.pythonhosted.org/packages/f0/30/2e1841a19b4dd23878d73edd5d82e998a83d5ed9570a89675f140ca8b2ad/rignore-0.7.6-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:690a3e1b54bfe77e89c4bacb13f046e642f8baadafc61d68f5a726f324a76ab6", size = 874144 }, + { url = "https://files.pythonhosted.org/packages/c2/bf/0ce9beb2e5f64c30e3580bef09f5829236889f01511a125f98b83169b993/rignore-0.7.6-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09d12ac7a0b6210c07bcd145007117ebd8abe99c8eeb383e9e4673910c2754b2", size = 1168062 }, + { url = "https://files.pythonhosted.org/packages/b9/8b/571c178414eb4014969865317da8a02ce4cf5241a41676ef91a59aab24de/rignore-0.7.6-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a2b2b74a8c60203b08452479b90e5ce3dbe96a916214bc9eb2e5af0b6a9beb0", size = 942542 }, + { url = "https://files.pythonhosted.org/packages/19/62/7a3cf601d5a45137a7e2b89d10c05b5b86499190c4b7ca5c3c47d79ee519/rignore-0.7.6-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc5a531ef02131e44359419a366bfac57f773ea58f5278c2cdd915f7d10ea94", size = 958739 }, + { url = "https://files.pythonhosted.org/packages/5f/1f/4261f6a0d7caf2058a5cde2f5045f565ab91aa7badc972b57d19ce58b14e/rignore-0.7.6-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7a1f77d9c4cd7e76229e252614d963442686bfe12c787a49f4fe481df49e7a9", size = 984138 }, + { url = "https://files.pythonhosted.org/packages/2b/bf/628dfe19c75e8ce1f45f7c248f5148b17dfa89a817f8e3552ab74c3ae812/rignore-0.7.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ead81f728682ba72b5b1c3d5846b011d3e0174da978de87c61645f2ed36659a7", size = 1079299 }, + { url = "https://files.pythonhosted.org/packages/af/a5/be29c50f5c0c25c637ed32db8758fdf5b901a99e08b608971cda8afb293b/rignore-0.7.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:12ffd50f520c22ffdabed8cd8bfb567d9ac165b2b854d3e679f4bcaef11a9441", size = 1139618 }, + { url = "https://files.pythonhosted.org/packages/2a/40/3c46cd7ce4fa05c20b525fd60f599165e820af66e66f2c371cd50644558f/rignore-0.7.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e5a16890fbe3c894f8ca34b0fcacc2c200398d4d46ae654e03bc9b3dbf2a0a72", size = 1117626 }, + { url = "https://files.pythonhosted.org/packages/8c/b9/aea926f263b8a29a23c75c2e0d8447965eb1879d3feb53cfcf84db67ed58/rignore-0.7.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3abab3bf99e8a77488ef6c7c9a799fac22224c28fe9f25cc21aa7cc2b72bfc0b", size = 1128144 }, + { url = "https://files.pythonhosted.org/packages/a4/f6/0d6242f8d0df7f2ecbe91679fefc1f75e7cd2072cb4f497abaab3f0f8523/rignore-0.7.6-cp314-cp314-win32.whl", hash = "sha256:eeef421c1782953c4375aa32f06ecae470c1285c6381eee2a30d2e02a5633001", size = 646385 }, + { url = "https://files.pythonhosted.org/packages/d5/38/c0dcd7b10064f084343d6af26fe9414e46e9619c5f3224b5272e8e5d9956/rignore-0.7.6-cp314-cp314-win_amd64.whl", hash = "sha256:6aeed503b3b3d5af939b21d72a82521701a4bd3b89cd761da1e7dc78621af304", size = 725738 }, + { url = "https://files.pythonhosted.org/packages/d9/7a/290f868296c1ece914d565757ab363b04730a728b544beb567ceb3b2d96f/rignore-0.7.6-cp314-cp314-win_arm64.whl", hash = "sha256:104f215b60b3c984c386c3e747d6ab4376d5656478694e22c7bd2f788ddd8304", size = 656008 }, + { url = "https://files.pythonhosted.org/packages/ca/d2/3c74e3cd81fe8ea08a8dcd2d755c09ac2e8ad8fe409508904557b58383d3/rignore-0.7.6-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bb24a5b947656dd94cb9e41c4bc8b23cec0c435b58be0d74a874f63c259549e8", size = 882835 }, + { url = "https://files.pythonhosted.org/packages/77/61/a772a34b6b63154877433ac2d048364815b24c2dd308f76b212c408101a2/rignore-0.7.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b1e33c9501cefe24b70a1eafd9821acfd0ebf0b35c3a379430a14df089993e3", size = 820301 }, + { url = "https://files.pythonhosted.org/packages/71/30/054880b09c0b1b61d17eeb15279d8bf729c0ba52b36c3ada52fb827cbb3c/rignore-0.7.6-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bec3994665a44454df86deb762061e05cd4b61e3772f5b07d1882a8a0d2748d5", size = 897611 }, + { url = "https://files.pythonhosted.org/packages/1e/40/b2d1c169f833d69931bf232600eaa3c7998ba4f9a402e43a822dad2ea9f2/rignore-0.7.6-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26cba2edfe3cff1dfa72bddf65d316ddebf182f011f2f61538705d6dbaf54986", size = 873875 }, + { url = "https://files.pythonhosted.org/packages/55/59/ca5ae93d83a1a60e44b21d87deb48b177a8db1b85e82fc8a9abb24a8986d/rignore-0.7.6-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffa86694fec604c613696cb91e43892aa22e1fec5f9870e48f111c603e5ec4e9", size = 1167245 }, + { url = "https://files.pythonhosted.org/packages/a5/52/cf3dce392ba2af806cba265aad6bcd9c48bb2a6cb5eee448d3319f6e505b/rignore-0.7.6-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48efe2ed95aa8104145004afb15cdfa02bea5cdde8b0344afeb0434f0d989aa2", size = 941750 }, + { url = "https://files.pythonhosted.org/packages/ec/be/3f344c6218d779395e785091d05396dfd8b625f6aafbe502746fcd880af2/rignore-0.7.6-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dcae43eb44b7f2457fef7cc87f103f9a0013017a6f4e62182c565e924948f21", size = 958896 }, + { url = "https://files.pythonhosted.org/packages/c9/34/d3fa71938aed7d00dcad87f0f9bcb02ad66c85d6ffc83ba31078ce53646a/rignore-0.7.6-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2cd649a7091c0dad2f11ef65630d30c698d505cbe8660dd395268e7c099cc99f", size = 983992 }, + { url = "https://files.pythonhosted.org/packages/24/a4/52a697158e9920705bdbd0748d59fa63e0f3233fb92e9df9a71afbead6ca/rignore-0.7.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42de84b0289d478d30ceb7ae59023f7b0527786a9a5b490830e080f0e4ea5aeb", size = 1078181 }, + { url = "https://files.pythonhosted.org/packages/ac/65/aa76dbcdabf3787a6f0fd61b5cc8ed1e88580590556d6c0207960d2384bb/rignore-0.7.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:875a617e57b53b4acbc5a91de418233849711c02e29cc1f4f9febb2f928af013", size = 1139232 }, + { url = "https://files.pythonhosted.org/packages/08/44/31b31a49b3233c6842acc1c0731aa1e7fb322a7170612acf30327f700b44/rignore-0.7.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8703998902771e96e49968105207719f22926e4431b108450f3f430b4e268b7c", size = 1117349 }, + { url = "https://files.pythonhosted.org/packages/e9/ae/1b199a2302c19c658cf74e5ee1427605234e8c91787cfba0015f2ace145b/rignore-0.7.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:602ef33f3e1b04c1e9a10a3c03f8bc3cef2d2383dcc250d309be42b49923cabc", size = 1127702 }, + { url = "https://files.pythonhosted.org/packages/fc/d3/18210222b37e87e36357f7b300b7d98c6dd62b133771e71ae27acba83a4f/rignore-0.7.6-cp314-cp314t-win32.whl", hash = "sha256:c1d8f117f7da0a4a96a8daef3da75bc090e3792d30b8b12cfadc240c631353f9", size = 647033 }, + { url = "https://files.pythonhosted.org/packages/3e/87/033eebfbee3ec7d92b3bb1717d8f68c88e6fc7de54537040f3b3a405726f/rignore-0.7.6-cp314-cp314t-win_amd64.whl", hash = "sha256:ca36e59408bec81de75d307c568c2d0d410fb880b1769be43611472c61e85c96", size = 725647 }, + { url = "https://files.pythonhosted.org/packages/79/62/b88e5879512c55b8ee979c666ee6902adc4ed05007226de266410ae27965/rignore-0.7.6-cp314-cp314t-win_arm64.whl", hash = "sha256:b83adabeb3e8cf662cabe1931b83e165b88c526fa6af6b3aa90429686e474896", size = 656035 }, + { url = "https://files.pythonhosted.org/packages/82/78/a6250ff0c49a3cdb943910ada4116e708118e9b901c878cfae616c80a904/rignore-0.7.6-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a20b6fb61bcced9a83dfcca6599ad45182b06ba720cff7c8d891e5b78db5b65f", size = 886470 }, + { url = "https://files.pythonhosted.org/packages/35/af/c69c0c51b8f9f7914d95c4ea91c29a2ac067572048cae95dd6d2efdbe05d/rignore-0.7.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:392dcabfecbe176c9ebbcb40d85a5e86a5989559c4f988c2741da7daf1b5be25", size = 825976 }, + { url = "https://files.pythonhosted.org/packages/f1/d2/1b264f56132264ea609d3213ab603d6a27016b19559a1a1ede1a66a03dcd/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22baa462abdc36fdd5a5e2dae423107723351b85ff093762f9261148b9d0a04a", size = 899739 }, + { url = "https://files.pythonhosted.org/packages/55/e4/b3c5dfdd8d8a10741dfe7199ef45d19a0e42d0c13aa377c83bd6caf65d90/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53fb28882d2538cb2d231972146c4927a9d9455e62b209f85d634408c4103538", size = 874843 }, + { url = "https://files.pythonhosted.org/packages/cc/10/d6f3750233881a2a154cefc9a6a0a9b19da526b19f7f08221b552c6f827d/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87409f7eeb1103d6b77f3472a3a0d9a5953e3ae804a55080bdcb0120ee43995b", size = 1170348 }, + { url = "https://files.pythonhosted.org/packages/6e/10/ad98ca05c9771c15af734cee18114a3c280914b6e34fde9ffea2e61e88aa/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684014e42e4341ab3ea23a203551857fcc03a7f8ae96ca3aefb824663f55db32", size = 942315 }, + { url = "https://files.pythonhosted.org/packages/de/00/ab5c0f872acb60d534e687e629c17e0896c62da9b389c66d3aa16b817aa8/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77356ebb01ba13f8a425c3d30fcad40e57719c0e37670d022d560884a30e4767", size = 961047 }, + { url = "https://files.pythonhosted.org/packages/b8/86/3030fdc363a8f0d1cd155b4c453d6db9bab47a24fcc64d03f61d9d78fe6a/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6cbd8a48abbd3747a6c830393cd578782fab5d43f4deea48c5f5e344b8fed2b0", size = 986090 }, + { url = "https://files.pythonhosted.org/packages/33/b8/133aa4002cee0ebbb39362f94e4898eec7fbd09cec9fcbce1cd65b355b7f/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2673225dcec7f90497e79438c35e34638d0d0391ccea3cbb79bfb9adc0dc5bd7", size = 1079656 }, + { url = "https://files.pythonhosted.org/packages/67/56/36d5d34210e5e7dfcd134eed8335b19e80ae940ee758f493e4f2b344dd70/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:c081f17290d8a2b96052b79207622aa635686ea39d502b976836384ede3d303c", size = 1139789 }, + { url = "https://files.pythonhosted.org/packages/6b/5b/bb4f9420802bf73678033a4a55ab1bede36ce2e9b41fec5f966d83d932b3/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:57e8327aacc27f921968cb2a174f9e47b084ce9a7dd0122c8132d22358f6bd79", size = 1120308 }, + { url = "https://files.pythonhosted.org/packages/ce/8b/a1299085b28a2f6135e30370b126e3c5055b61908622f2488ade67641479/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:d8955b57e42f2a5434670d5aa7b75eaf6e74602ccd8955dddf7045379cd762fb", size = 1129444 }, ] [[package]] @@ -2523,15 +2527,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.43.0" +version = "2.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/18/09875b4323b03ca9025bae7e6539797b27e4fc032998a466b4b9c3d24653/sentry_sdk-2.43.0.tar.gz", hash = "sha256:52ed6e251c5d2c084224d73efee56b007ef5c2d408a4a071270e82131d336e20", size = 368953 } +sdist = { url = "https://files.pythonhosted.org/packages/62/26/ff7d93a14a0ec309021dca2fb7c62669d4f6f5654aa1baf60797a16681e0/sentry_sdk-2.44.0.tar.gz", hash = "sha256:5b1fe54dfafa332e900b07dd8f4dfe35753b64e78e7d9b1655a28fd3065e2493", size = 371464 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/31/8228fa962f7fd8814d634e4ebece8780e2cdcfbdf0cd2e14d4a6861a7cd5/sentry_sdk-2.43.0-py2.py3-none-any.whl", hash = "sha256:4aacafcf1756ef066d359ae35030881917160ba7f6fc3ae11e0e58b09edc2d5d", size = 400997 }, + { url = "https://files.pythonhosted.org/packages/a8/56/c16bda4d53012c71fa1b588edde603c6b455bc8206bf6de7b83388fcce75/sentry_sdk-2.44.0-py2.py3-none-any.whl", hash = "sha256:9e36a0372b881e8f92fdbff4564764ce6cec4b7f25424d0a3a8d609c9e4651a7", size = 402352 }, ] [[package]] @@ -2627,15 +2631,15 @@ wheels = [ [[package]] name = "starlette" -version = "0.49.1" +version = "0.49.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/3f/507c21db33b66fb027a332f2cb3abbbe924cc3a79ced12f01ed8645955c9/starlette-0.49.1.tar.gz", hash = "sha256:481a43b71e24ed8c43b11ea02f5353d77840e01480881b8cb5a26b8cae64a8cb", size = 2654703 } +sdist = { url = "https://files.pythonhosted.org/packages/de/1a/608df0b10b53b0beb96a37854ee05864d182ddd4b1156a22f1ad3860425a/starlette-0.49.3.tar.gz", hash = "sha256:1c14546f299b5901a1ea0e34410575bc33bbd741377a10484a54445588d00284", size = 2655031 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/da/545b75d420bb23b5d494b0517757b351963e974e79933f01e05c929f20a6/starlette-0.49.1-py3-none-any.whl", hash = "sha256:d92ce9f07e4a3caa3ac13a79523bd18e3bc0042bb8ff2d759a8e7dd0e1859875", size = 74175 }, + { url = "https://files.pythonhosted.org/packages/a3/e0/021c772d6a662f43b63044ab481dc6ac7592447605b5b35a957785363122/starlette-0.49.3-py3-none-any.whl", hash = "sha256:b579b99715fdc2980cf88c8ec96d3bf1ce16f5a8051a7c2b84ef9b1cdecaea2f", size = 74340 }, ] [[package]]